diff options
| author | Monroe Williams <monroe@lindenlab.com> | 2009-02-27 21:01:19 +0000 | 
|---|---|---|
| committer | Monroe Williams <monroe@lindenlab.com> | 2009-02-27 21:01:19 +0000 | 
| commit | dd437009e88954fd0fe9dd95b903dbd1ea52e901 (patch) | |
| tree | 5f5cb97ebc5dc4ca3bdbe2867a897e625c54a011 /indra/newview | |
| parent | 0bd557510a20565a4f27318f86dd11dac88ff574 (diff) | |
svn merge -r 113014:113017 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-QAR-1323
Merging in QAR-1323.
Diffstat (limited to 'indra/newview')
| -rw-r--r-- | indra/newview/app_settings/settings.xml | 30 | ||||
| -rw-r--r-- | indra/newview/installers/darwin/dmg-cleanup.applescript | 28 | ||||
| -rw-r--r-- | indra/newview/llfloaterfriends.cpp | 40 | ||||
| -rw-r--r-- | indra/newview/llimpanel.cpp | 88 | ||||
| -rw-r--r-- | indra/newview/llimpanel.h | 4 | ||||
| -rw-r--r-- | indra/newview/llimview.cpp | 12 | ||||
| -rw-r--r-- | indra/newview/llimview.h | 6 | ||||
| -rw-r--r-- | indra/newview/llviewercontrol.cpp | 1 | ||||
| -rw-r--r-- | indra/newview/llvoiceclient.cpp | 4654 | ||||
| -rw-r--r-- | indra/newview/llvoiceclient.h | 439 | ||||
| -rw-r--r-- | indra/newview/skins/default/textures/slim_icon_16_viewer.tga | bin | 0 -> 1032 bytes | |||
| -rwxr-xr-x | indra/newview/viewer_manifest.py | 6 | 
12 files changed, 4140 insertions, 1168 deletions
| diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 523032bf67..e964799e4c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10198,6 +10198,17 @@        <key>Value</key>        <integer>0</integer>      </map> +    <key>VivoxAutoPostCrashDumps</key> +    <map> +      <key>Comment</key> +      <string>If true, SLVoice will automatically send crash dumps directly to Vivox.</string> +      <key>Persist</key> +      <integer>1</integer> +      <key>Type</key> +      <string>Boolean</string> +      <key>Value</key> +      <integer>0</integer> +    </map>      <key>VivoxDebugLevel</key>      <map>        <key>Comment</key> @@ -10209,16 +10220,27 @@        <key>Value</key>        <string>-1</string>      </map> -    <key>VivoxDebugServerName</key> +    <key>VivoxDebugSIPURIHostName</key> +    <map> +      <key>Comment</key> +      <string>Hostname portion of vivox SIP URIs (empty string for the default).</string> +      <key>Persist</key> +      <integer>1</integer> +      <key>Type</key> +      <string>String</string> +      <key>Value</key> +      <string></string> +    </map> +    <key>VivoxDebugVoiceAccountServerURI</key>      <map>        <key>Comment</key> -      <string>Hostname of the vivox account server to use for voice when not connected to Agni.</string> +      <string>URI to the vivox account management server (empty string for the default).</string>        <key>Persist</key>        <integer>1</integer>        <key>Type</key>        <string>String</string>        <key>Value</key> -      <string>bhd.vivox.com</string> +      <string></string>      </map>      <key>VoiceCallsFriendsOnly</key>      <map> @@ -10361,7 +10383,7 @@        <key>Type</key>        <string>U32</string>        <key>Value</key> -      <integer>44124</integer> +      <integer>44125</integer>      </map>      <key>WLSkyDetail</key>      <map> diff --git a/indra/newview/installers/darwin/dmg-cleanup.applescript b/indra/newview/installers/darwin/dmg-cleanup.applescript new file mode 100644 index 0000000000..f3d39aec21 --- /dev/null +++ b/indra/newview/installers/darwin/dmg-cleanup.applescript @@ -0,0 +1,28 @@ +-- First, convert the disk image to "read-write" format with Disk Utility or hdiutil +-- Mount the image, open the disk image window in the Finder and make it frontmost, then run this script from inside Script Editor +-- After running the script, unmount the disk image, re-mount it, and copy the .DS_Store file off from the command line. + +tell application "Finder" +	 +	set foo to every item in front window +	repeat with i in foo +		if the name of i is "Applications" then +			set the position of i to {391, 165} +		else if the name of i ends with ".app" then +			set the position of i to {121, 166} +		end if +	end repeat +	 +	-- There doesn't seem to be a way to set the background picture with applescript, but all the saved .DS_Store files should already have that set correctly. +	 +	set foo to front window +	set current view of foo to icon view +	set toolbar visible of foo to false +	set statusbar visible of foo to false +	set the bounds of foo to {100, 100, 600, 399} +	 +	-- set the position of front window to {100, 100} +	-- get {name, position} of every item of front window +	 +	get properties of front window +end tell diff --git a/indra/newview/llfloaterfriends.cpp b/indra/newview/llfloaterfriends.cpp index f5133584b5..014a631a53 100644 --- a/indra/newview/llfloaterfriends.cpp +++ b/indra/newview/llfloaterfriends.cpp @@ -58,6 +58,7 @@  #include "llviewermessage.h"  #include "lltimer.h"  #include "lltextbox.h" +#include "llvoiceclient.h"  //Maximum number of people you can select to do an operation on at once.  #define MAX_FRIEND_SELECT 20 @@ -65,6 +66,8 @@  #define RIGHTS_CHANGE_TIMEOUT 5.0  #define OBSERVER_TIMEOUT 0.5 +#define ONLINE_SIP_ICON_NAME "slim_icon_16_viewer.tga" +  // simple class to observe the calling cards.  class LLLocalFriendsObserver : public LLFriendObserver, public LLEventTimer  { @@ -112,10 +115,14 @@ LLPanelFriends::LLPanelFriends() :  	mEventTimer.stop();  	mObserver = new LLLocalFriendsObserver(this);  	LLAvatarTracker::instance().addObserver(mObserver); +	// For notification when SIP online status changes. +	LLVoiceClient::getInstance()->addObserver(mObserver);  }  LLPanelFriends::~LLPanelFriends()  { +	// For notification when SIP online status changes. +	LLVoiceClient::getInstance()->removeObserver(mObserver);  	LLAvatarTracker::instance().removeObserver(mObserver);  	delete mObserver;  } @@ -213,7 +220,9 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id)  	LLAvatarTracker& at = LLAvatarTracker::instance();  	const LLRelationship* relationInfo = at.getBuddyInfo(agent_id);  	if(!relationInfo) return FALSE; -	BOOL online = relationInfo->isOnline(); + +	bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(agent_id); +	bool isOnline = relationInfo->isOnline();  	std::string fullname;  	BOOL have_name = gCacheName->getFullName(agent_id, fullname); @@ -229,12 +238,17 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id)  	LLSD& online_status_column = element["columns"][LIST_ONLINE_STATUS];  	online_status_column["column"] = "icon_online_status";  	online_status_column["type"] = "icon"; - -	if (online) +	 +	if (isOnline)  	{  		friend_column["font-style"] = "BOLD";	  		online_status_column["value"] = "icon_avatar_online.tga";  	} +	else if(isOnlineSIP) +	{ +		friend_column["font-style"] = "BOLD";	 +		online_status_column["value"] = ONLINE_SIP_ICON_NAME; +	}  	LLSD& online_column = element["columns"][LIST_VISIBLE_ONLINE];  	online_column["column"] = "icon_visible_online"; @@ -272,14 +286,30 @@ BOOL LLPanelFriends::updateFriendItem(const LLUUID& agent_id, const LLRelationsh  	if (!info) return FALSE;  	LLScrollListItem* itemp = mFriendsList->getItem(agent_id);  	if (!itemp) return FALSE; +	 +	bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(itemp->getUUID()); +	bool isOnline = info->isOnline();  	std::string fullname;  	BOOL have_name = gCacheName->getFullName(agent_id, fullname); +	 +	// Name of the status icon to use +	std::string statusIcon; +	 +	if(isOnline) +	{ +		statusIcon = "icon_avatar_online.tga"; +	} +	else if(isOnlineSIP) +	{ +		statusIcon = ONLINE_SIP_ICON_NAME; +	} -	itemp->getColumn(LIST_ONLINE_STATUS)->setValue(info->isOnline() ? std::string("icon_avatar_online.tga") : LLStringUtil::null); +	itemp->getColumn(LIST_ONLINE_STATUS)->setValue(statusIcon); +	  	itemp->getColumn(LIST_FRIEND_NAME)->setValue(fullname);  	// render name of online friends in bold text -	((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle(info->isOnline() ? LLFontGL::BOLD : LLFontGL::NORMAL);	 +	((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle((isOnline || isOnlineSIP) ? LLFontGL::BOLD : LLFontGL::NORMAL);	  	itemp->getColumn(LIST_VISIBLE_ONLINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS));  	itemp->getColumn(LIST_VISIBLE_MAP)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION));  	itemp->getColumn(LIST_EDIT_MINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index bded46b23b..de9e92fcdd 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -358,7 +358,7 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess  		llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl;  	} -	LLVoiceClient::getInstance()->addStatusObserver(this); +	LLVoiceClient::getInstance()->addObserver(this);  }  LLVoiceChannel::~LLVoiceChannel() @@ -366,7 +366,7 @@ LLVoiceChannel::~LLVoiceChannel()  	// Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed.  	if(gVoiceClient)  	{ -		gVoiceClient->removeStatusObserver(this); +		gVoiceClient->removeObserver(this);  	}  	sVoiceChannelMap.erase(mSessionID); @@ -985,7 +985,8 @@ void LLVoiceChannelP2P::activate()  		// otherwise answering the call  		else  		{ -			LLVoiceClient::getInstance()->answerInvite(mSessionHandle, mOtherUserID); +			LLVoiceClient::getInstance()->answerInvite(mSessionHandle); +			  			// using the session handle invalidates it.  Clear it out here so we can't reuse it by accident.  			mSessionHandle.clear();  		} @@ -1002,7 +1003,7 @@ void LLVoiceChannelP2P::getChannelInfo()  }  // receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle) +void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI)  {   	BOOL needs_activate = FALSE;  	if (callStarted()) @@ -1025,8 +1026,17 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle)  	}  	mSessionHandle = handle; +  	// The URI of a p2p session should always be the other end's SIP URI. -	setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); +	if(!inURI.empty()) +	{ +		setURI(inURI); +	} +	else +	{ +		setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); +	} +	  	mReceivedCall = TRUE;  	if (needs_activate) @@ -1209,7 +1219,23 @@ LLFloaterIMPanel::~LLFloaterIMPanel()  {  	delete mSpeakers;  	mSpeakers = NULL; - +	 +	// End the text IM session if necessary +	if(gVoiceClient && mOtherParticipantUUID.notNull()) +	{ +		switch(mDialog) +		{ +			case IM_NOTHING_SPECIAL: +			case IM_SESSION_P2P_INVITE: +				gVoiceClient->endUserIMSession(mOtherParticipantUUID); +			break; +			 +			default: +				// Appease the compiler +			break; +		} +	} +	  	//kicks you out of the voice channel if it is currently active  	// HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). @@ -1872,33 +1898,45 @@ void deliver_message(const std::string& utf8_text,  					 EInstantMessage dialog)  {  	std::string name; +	bool sent = false;  	gAgent.buildFullname(name);  	const LLRelationship* info = NULL;  	info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); +	  	U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; - -	// default to IM_SESSION_SEND unless it's nothing special - in -	// which case it's probably an IM to everyone. -	U8 new_dialog = dialog; - -	if ( dialog != IM_NOTHING_SPECIAL ) +	 +	if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id)))  	{ -		new_dialog = IM_SESSION_SEND; +		// User is online through the OOW connector, but not with a regular viewer.  Try to send the message via SLVoice. +		sent = gVoiceClient->sendTextMessage(other_participant_id, utf8_text);  	} +	 +	if(!sent) +	{ +		// Send message normally. -	pack_instant_message( -		gMessageSystem, -		gAgent.getID(), -		FALSE, -		gAgent.getSessionID(), -		other_participant_id, -		name, -		utf8_text, -		offline, -		(EInstantMessage)new_dialog, -		im_session_id); -	gAgent.sendReliableMessage(); +		// default to IM_SESSION_SEND unless it's nothing special - in +		// which case it's probably an IM to everyone. +		U8 new_dialog = dialog; + +		if ( dialog != IM_NOTHING_SPECIAL ) +		{ +			new_dialog = IM_SESSION_SEND; +		} +		pack_instant_message( +			gMessageSystem, +			gAgent.getID(), +			FALSE, +			gAgent.getSessionID(), +			other_participant_id, +			name.c_str(), +			utf8_text.c_str(), +			offline, +			(EInstantMessage)new_dialog, +			im_session_id); +		gAgent.sendReliableMessage(); +	}  	// If there is a mute list and this is not a group chat...  	if ( LLMuteList::getInstance() ) diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h index 176d11c8f9..e54cec56c7 100644 --- a/indra/newview/llimpanel.h +++ b/indra/newview/llimpanel.h @@ -162,7 +162,7 @@ public:      /*virtual*/ void activate();  	/*virtual*/ void getChannelInfo(); -	void setSessionHandle(const std::string& handle); +	void setSessionHandle(const std::string& handle, const std::string &inURI);  protected:  	virtual void setState(EState state); @@ -295,8 +295,6 @@ private:  	void sendTypingState(BOOL typing); -	static LLFloaterIMPanel* sInstance; -  private:  	LLLineEditor* mInputEditor;  	LLViewerTextEditor* mHistoryEditor; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 2b9863819a..a90ea39265 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -364,7 +364,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)  				session_id = gIMMgr->addP2PSession(  					payload["session_name"].asString(),  					payload["caller_id"].asUUID(), -					payload["session_handle"].asString()); +					payload["session_handle"].asString(), +					payload["session_uri"].asString());  				LLFloaterIMPanel* im_floater =  					gIMMgr->findFloaterBySession( @@ -725,7 +726,8 @@ BOOL LLIMMgr::isIMSessionOpen(const LLUUID& uuid)  LLUUID LLIMMgr::addP2PSession(const std::string& name,  							const LLUUID& other_participant_id, -							const std::string& voice_session_handle) +							const std::string& voice_session_handle, +							const std::string& caller_uri)  {  	LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id); @@ -733,7 +735,7 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name,  	if(floater)  	{  		LLVoiceChannelP2P* voice_channelp = (LLVoiceChannelP2P*)floater->getVoiceChannel(); -		voice_channelp->setSessionHandle(voice_session_handle); +		voice_channelp->setSessionHandle(voice_session_handle, caller_uri);		  	}  	return session_id; @@ -856,7 +858,8 @@ void LLIMMgr::inviteToSession(  	const std::string& caller_name,  	EInstantMessage type,  	EInvitationType inv_type, -	const std::string& session_handle) +	const std::string& session_handle, +	const std::string& session_uri)  {  	//ignore invites from muted residents  	if (LLMuteList::getInstance()->isMuted(caller_id)) @@ -898,6 +901,7 @@ void LLIMMgr::inviteToSession(  	payload["type"] = type;  	payload["inv_type"] = inv_type;  	payload["session_handle"] = session_handle; +	payload["session_uri"] = session_uri;  	payload["notify_box_type"] = notify_box_type;  	LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id); diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 88109a9154..a4e419694d 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -98,7 +98,8 @@ public:  	// Creates a P2P session with the requisite handle for responding to voice calls  	LLUUID addP2PSession(const std::string& name,  					  const LLUUID& other_participant_id, -					  const std::string& voice_session_handle); +					  const std::string& voice_session_handle, +					  const std::string& caller_uri = LLStringUtil::null);  	// This removes the panel referenced by the uuid, and then  	// restores internal consistency. The internal pointer is not @@ -112,7 +113,8 @@ public:  		const std::string& caller_name,  		EInstantMessage type,  		EInvitationType inv_type,  -		const std::string& session_handle = LLStringUtil::null); +		const std::string& session_handle = LLStringUtil::null, +		const std::string& session_uri = LLStringUtil::null);  	//Updates a given session's session IDs.  Does not open,  	//create or do anything new.  If the old session doesn't diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 1a40da9c1f..6b99cfbeaf 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -560,7 +560,6 @@ void settings_setup_listeners()  	gSavedSettings.getControl("PushToTalkButton")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));  	gSavedSettings.getControl("PushToTalkToggle")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));  	gSavedSettings.getControl("VoiceEarLocation")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); -	gSavedSettings.getControl("VivoxDebugServerName")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));  	gSavedSettings.getControl("VoiceInputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));  	gSavedSettings.getControl("VoiceOutputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));  	gSavedSettings.getControl("AudioLevelMic")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 1d62ec4b9a..3bcc0af7d5 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -63,15 +63,20 @@  #include "llviewerwindow.h"  #include "llviewercamera.h" +#include "llfloaterfriends.h"  //VIVOX, inorder to refresh communicate panel +#include "llfloaterchat.h"		// for LLFloaterChat::addChat() +  // for base64 decoding  #include "apr_base64.h"  // for SHA1 hash  #include "apr_sha1.h" -// If we are connecting to agni AND the user's last name is "Linden", join this channel instead of looking up the sim name. -// If we are connecting to agni and the user's last name is NOT "Linden", disable voice. -#define AGNI_LINDENS_ONLY_CHANNEL "SL" +// for MD5 hash +#include "llmd5.h" + +#define USE_SESSION_GROUPS 0 +  static bool sConnectingToAgni = false;  F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; @@ -91,6 +96,44 @@ const F32 UPDATE_THROTTLE_SECONDS = 0.1f;  const F32 LOGIN_RETRY_SECONDS = 10.0f;  const int MAX_LOGIN_RETRIES = 12; +static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str) +{ +	LLMD5 md5_uuid; +	md5_uuid.update((const unsigned char*)str.data(), str.size()); +	md5_uuid.finalize(); +	md5_uuid.raw_digest(uuid.mData); +} + +static int scale_mic_volume(float volume) +{ +	// incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. +	// Map it as follows: 0.0 -> 40, 1.0 -> 44, 2.0 -> 75 + +	volume -= 1.0f;		// offset volume to the range [-1.0 ... 1.0], with 0 at the default. +	int scaled_volume = 44;	// offset scaled_volume by its default level +	if(volume < 0.0f) +		scaled_volume += ((int)(volume * 4.0f));	// (44 - 40) +	else +		scaled_volume += ((int)(volume * 31.0f));	// (75 - 44) +	 +	return scaled_volume; +} + +static int scale_speaker_volume(float volume) +{ +	// incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. +	// Map it as follows: 0.0 -> 0, 0.5 -> 62, 1.0 -> 75 +	 +	volume -= 0.5f;		// offset volume to the range [-0.5 ... 0.5], with 0 at the default. +	int scaled_volume = 62;	// offset scaled_volume by its default level +	if(volume < 0.0f) +		scaled_volume += ((int)(volume * 124.0f));	// (62 - 0) * 2 +	else +		scaled_volume += ((int)(volume * 26.0f));	// (75 - 62) * 2 +	 +	return scaled_volume; +} +  class LLViewerVoiceAccountProvisionResponder :  	public LLHTTPClient::Responder  { @@ -104,12 +147,13 @@ public:  	{  		if ( mRetries > 0 )  		{ +			LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying.  status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;  			if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision(  				mRetries - 1);  		}  		else  		{ -			//TODO: throw an error message? +			LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up).  status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;  			if ( gVoiceClient ) gVoiceClient->giveUp();  		}  	} @@ -118,9 +162,23 @@ public:  	{  		if ( gVoiceClient )  		{ +			std::string voice_sip_uri_hostname; +			std::string voice_account_server_uri; +			 +			LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; +			 +			if(content.has("voice_sip_uri_hostname")) +				voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString(); +			 +			// this key is actually misnamed -- it will be an entire URI, not just a hostname. +			if(content.has("voice_account_server_name")) +				voice_account_server_uri = content["voice_account_server_name"].asString(); +			  			gVoiceClient->login(  				content["username"].asString(), -				content["password"].asString()); +				content["password"].asString(), +				voice_sip_uri_hostname, +				voice_account_server_uri);  		}  	} @@ -167,21 +225,27 @@ protected:  	int				ignoreDepth;  	// Members for processing responses. The values are transient and only valid within a call to processResponse(). +	bool			squelchDebugOutput;  	int				returnCode;  	int				statusCode;  	std::string		statusString; -	std::string		uuidString; +	std::string		requestId;  	std::string		actionString;  	std::string		connectorHandle; +	std::string		versionID;  	std::string		accountHandle;  	std::string		sessionHandle; -	std::string		eventSessionHandle; +	std::string		sessionGroupHandle; +	std::string		alias; +	std::string		applicationString;  	// Members for processing events. The values are transient and only valid within a call to processResponse().  	std::string		eventTypeString;  	int				state;  	std::string		uriString;  	bool			isChannel; +	bool			incoming; +	bool			enabled;  	std::string		nameString;  	std::string		audioMediaString;  	std::string		displayNameString; @@ -191,6 +255,21 @@ protected:  	bool			isSpeaking;  	int				volume;  	F32				energy; +	std::string		messageHeader; +	std::string		messageBody; +	std::string		notificationType; +	bool			hasText; +	bool			hasAudio; +	bool			hasVideo; +	bool			terminated; +	std::string		blockMask; +	std::string		presenceOnly; +	std::string		autoAcceptMask; +	std::string		autoAddAsBuddy; +	int				numberOfAliases; +	std::string		subscriptionHandle; +	std::string		subscriptionType; +		  	// Members for processing text between tags  	std::string		textBuffer; @@ -223,8 +302,6 @@ void LLVivoxProtocolParser::reset()  	responseDepth = 0;  	ignoringTags = false;  	accumulateText = false; -	textBuffer.clear(); -  	energy = 0.f;  	ignoreDepth = 0;  	isChannel = false; @@ -233,10 +310,15 @@ void LLVivoxProtocolParser::reset()  	isModeratorMuted = false;  	isSpeaking = false;  	participantType = 0; -	returnCode = 0; +	squelchDebugOutput = false; +	returnCode = -1;  	state = 0;  	statusCode = 0;  	volume = 0; +	textBuffer.clear(); +	alias.clear(); +	numberOfAliases = 0; +	applicationString.clear();  }  //virtual  @@ -263,33 +345,11 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(  		mInput.append(buf, istr.gcount());  	} -	// MBW -- XXX -- This should no longer be necessary.  Or even possible. -	// We've read all the data out of the buffer.  Make sure it doesn't accumulate. -//	buffer->clear(); -	  	// 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)  	{	 -		// Turn this on to log incoming XML -		if(0) -		{ -			int foo = mInput.find("Set3DPosition", start); -			int bar = mInput.find("ParticipantPropertiesEvent", start); -			if(foo != std::string::npos && (foo < delim)) -			{ -				// This is a Set3DPosition response.  Don't print it, since these are way too spammy. -			} -			else if(bar != std::string::npos && (bar < delim)) -			{ -				// This is a ParticipantPropertiesEvent response.  Don't print it, since these are way too spammy. -			} -			else -			{ -				LL_INFOS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; -			} -		}  		// Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)  		reset(); @@ -300,13 +360,19 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(  		XML_SetUserData(parser, this);	  		XML_Parse(parser, mInput.data() + start, delim - start, false); +		// If this message isn't set to be squelched, output the raw XML received. +		if(!squelchDebugOutput) +		{ +			LL_DEBUGS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; +		} +		  		start = delim + 3;  	}  	if(start != 0)  		mInput = mInput.substr(start); -	LL_DEBUGS("Voice") << "at end, mInput is: " << mInput << LL_ENDL; +	LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL;  	if(!gVoiceClient->mConnected)  	{ @@ -361,9 +427,9 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)  	if (responseDepth == 0)  	{	 -		isEvent = strcmp("Event", tag) == 0; +		isEvent = !stricmp("Event", tag); -		if (strcmp("Response", tag) == 0 || isEvent) +		if (!stricmp("Response", tag) || isEvent)  		{  			// Grab the attributes  			while (*attr) @@ -371,49 +437,62 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)  				const char	*key = *attr++;  				const char	*value = *attr++; -				if (strcmp("requestId", key) == 0) +				if (!stricmp("requestId", key))  				{ -					uuidString = value; +					requestId = value;  				} -				else if (strcmp("action", key) == 0) +				else if (!stricmp("action", key))  				{  					actionString = value;  				} -				else if (strcmp("type", key) == 0) +				else if (!stricmp("type", key))  				{  					eventTypeString = value;  				}  			}  		} -		LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")"  << LL_ENDL; +		LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")"  << LL_ENDL;  	}  	else  	{  		if (ignoringTags)  		{ -			LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; +			LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;  		}  		else  		{ -			LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")"  << LL_ENDL; +			LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")"  << LL_ENDL;  			// Ignore the InputXml stuff so we don't get confused -			if (strcmp("InputXml", tag) == 0) +			if (!stricmp("InputXml", tag))  			{  				ignoringTags = true;  				ignoreDepth = responseDepth;  				accumulateText = false; -				LL_DEBUGS("Voice") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; +				LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;  			} -			else if (strcmp("CaptureDevices", tag) == 0) +			else if (!stricmp("CaptureDevices", tag))  			{  				gVoiceClient->clearCaptureDevices();  			} -			else if (strcmp("RenderDevices", tag) == 0) +			else if (!stricmp("RenderDevices", tag))  			{  				gVoiceClient->clearRenderDevices();  			} +			else if (!stricmp("Buddies", tag)) +			{ +				gVoiceClient->deleteAllBuddies(); +			} +			else if (!stricmp("BlockRules", tag)) +			{ +				gVoiceClient->deleteAllBlockRules(); +			} +			else if (!stricmp("AutoAcceptRules", tag)) +			{ +				gVoiceClient->deleteAllAutoAcceptRules(); +			} +			  		}  	}  	responseDepth++; @@ -432,90 +511,138 @@ void LLVivoxProtocolParser::EndTag(const char *tag)  	{  		if (ignoreDepth == responseDepth)  		{ -			LL_DEBUGS("Voice") << "end of ignore" << LL_ENDL; +			LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL;  			ignoringTags = false;  		}  		else  		{ -			LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; +			LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;  		}  	}  	if (!ignoringTags)  	{ -		LL_DEBUGS("Voice") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; +		LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;  		// Closing a tag. Finalize the text we've accumulated and reset -		if (strcmp("ReturnCode", tag) == 0) +		if (!stricmp("ReturnCode", tag))  			returnCode = strtol(string.c_str(), NULL, 10); -		else if (strcmp("StatusCode", tag) == 0) +		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 (strcmp("ConnectorHandle", tag) == 0) +		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 (strcmp("AccountHandle", tag) == 0) +		else if (!stricmp("VersionID", tag)) +			versionID = string; +		else if (!stricmp("AccountHandle", tag))  			accountHandle = string; -		else if (strcmp("SessionHandle", tag) == 0) -		{ -			if (isEvent) -				eventSessionHandle = string; -			else -				sessionHandle = string; -		} -		else if (strcmp("StatusString", tag) == 0) -			statusString = string; -		else if (strcmp("State", tag) == 0) +		else if (!stricmp("State", tag))  			state = strtol(string.c_str(), NULL, 10); -		else if (strcmp("URI", tag) == 0) +		else if (!stricmp("URI", tag))  			uriString = string; -		else if (strcmp("IsChannel", tag) == 0) -			isChannel = string == "true" ? true : false; -		else if (strcmp("Name", tag) == 0) +		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 (strcmp("AudioMedia", tag) == 0) +		else if (!stricmp("AudioMedia", tag))  			audioMediaString = string; -		else if (strcmp("ChannelName", tag) == 0) +		else if (!stricmp("ChannelName", tag))  			nameString = string; -		else if (strcmp("ParticipantURI", tag) == 0) -			uriString = string; -		else if (strcmp("DisplayName", tag) == 0) +		else if (!stricmp("DisplayName", tag))  			displayNameString = string; -		else if (strcmp("AccountName", tag) == 0) +		else if (!stricmp("AccountName", tag))  			nameString = string; -		else if (strcmp("ParticipantTyppe", tag) == 0) +		else if (!stricmp("ParticipantType", tag))  			participantType = strtol(string.c_str(), NULL, 10); -		else if (strcmp("IsLocallyMuted", tag) == 0) -			isLocallyMuted = string == "true" ? true : false; -		else if (strcmp("IsModeratorMuted", tag) == 0) -			isModeratorMuted = string == "true" ? true : false; -		else if (strcmp("IsSpeaking", tag) == 0) -			isSpeaking = string == "true" ? true : false; -		else if (strcmp("Volume", tag) == 0) -			volume = strtol(string.c_str(), NULL, 10); -		else if (strcmp("Energy", tag) == 0) -			energy = (F32)strtod(string.c_str(), NULL); -		else if (strcmp("MicEnergy", tag) == 0) +		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 (strcmp("ChannelName", tag) == 0) +		else if (!stricmp("ChannelName", tag))  			nameString = string; -		else if (strcmp("ChannelURI", tag) == 0) +		else if (!stricmp("ChannelURI", tag))  			uriString = string; -		else if (strcmp("ChannelListResult", tag) == 0) -		{ -			gVoiceClient->addChannelMapEntry(nameString, uriString); -		} -		else if (strcmp("Device", tag) == 0) +		else if (!stricmp("BuddyURI", tag)) +			uriString = string; +		else if (!stricmp("Presence", tag)) +			statusString = string; +		else if (!stricmp("Device", tag))  		{  			// This closing tag shouldn't clear the accumulated text.  			clearbuffer = false;  		} -		else if (strcmp("CaptureDevice", tag) == 0) +		else if (!stricmp("CaptureDevice", tag))  		{  			gVoiceClient->addCaptureDevice(textBuffer);  		} -		else if (strcmp("RenderDevice", tag) == 0) +		else if (!stricmp("RenderDevice", tag))  		{  			gVoiceClient->addRenderDevice(textBuffer);  		} +		else if (!stricmp("Buddy", tag)) +		{ +			gVoiceClient->processBuddyListEntry(uriString, displayNameString); +		} +		else if (!stricmp("BlockRule", tag)) +		{ +			gVoiceClient->addBlockRule(blockMask, presenceOnly); +		} +		else if (!stricmp("BlockMask", tag)) +			blockMask = string; +		else if (!stricmp("PresenceOnly", tag)) +			presenceOnly = string; +		else if (!stricmp("AutoAcceptRule", tag)) +		{ +			gVoiceClient->addAutoAcceptRule(autoAcceptMask, autoAddAsBuddy); +		} +		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; +		  		if(clearbuffer)  		{ @@ -550,144 +677,296 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length)  void LLVivoxProtocolParser::processResponse(std::string tag)  { -	LL_DEBUGS("Voice") << tag << LL_ENDL; +	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)  	{ -		if (eventTypeString == "LoginStateChangeEvent") +		const char *eventTypeCstr = eventTypeString.c_str(); +		if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent"))  		{ -			gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state); +			gVoiceClient->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state);  		} -		else if (eventTypeString == "SessionNewEvent") +		else if (!stricmp(eventTypeCstr, "SessionAddedEvent"))  		{ -			gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString); +			/* +			<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> +			*/ +			gVoiceClient->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString);  		} -		else if (eventTypeString == "SessionStateChangeEvent") +		else if (!stricmp(eventTypeCstr, "SessionRemovedEvent"))  		{ -			gVoiceClient->sessionStateChangeEvent(uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString); +			gVoiceClient->sessionRemovedEvent(sessionHandle, sessionGroupHandle);  		} -		else if (eventTypeString == "ParticipantStateChangeEvent") +		else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent"))  		{ -			gVoiceClient->participantStateChangeEvent(uriString, statusCode, statusString, state,  nameString, displayNameString, participantType); -			 +			gVoiceClient->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> +			*/ +			gVoiceClient->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); +		}		 +		else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent")) +		{ +			/* +			<Event type="TextStreamUpdatedEvent"> +				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1</SessionGroupHandle> +				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==1</SessionHandle> +				<Enabled>true</Enabled> +				<State>1</State> +				<Incoming>true</Incoming> +			</Event> +			*/ +			gVoiceClient->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming); +		} +		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> +			*/ +			gVoiceClient->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType);  		} -		else if (eventTypeString == "ParticipantPropertiesEvent") +		else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent"))  		{ -			gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); +			/* +			<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> +			*/ +			gVoiceClient->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString);  		} -		else if (eventTypeString == "AuxAudioPropertiesEvent") +		else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) +		{ +			/* +			<Event type="ParticipantUpdatedEvent"> +				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> +				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> +				<ParticipantUri>sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com</ParticipantUri> +				<IsModeratorMuted>false</IsModeratorMuted> +				<IsSpeaking>true</IsSpeaking> +				<Volume>44</Volume> +				<Energy>0.0879437</Energy> +			</Event> +			*/ +			 +			// These happen so often that logging them is pretty useless. +			squelchDebugOutput = true; +			 +			gVoiceClient->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); +		} +		else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))  		{  			gVoiceClient->auxAudioPropertiesEvent(energy);  		} +		else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent")) +		{ +			gVoiceClient->buddyPresenceEvent(uriString, alias, statusString, applicationString); +		} +		else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent")) +		{ +			// The buddy list was updated during parsing. +			// Need to recheck against the friends list. +			gVoiceClient->buddyListChanged(); +		} +		else if (!stricmp(eventTypeCstr, "BuddyChangedEvent")) +		{ +			/* +			<Event type="BuddyChangedEvent"> +				<AccountHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==</AccountHandle> +				<BuddyURI>sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com</BuddyURI> +				<DisplayName>Monroe Tester</DisplayName> +				<BuddyData /> +				<GroupID>0</GroupID> +				<ChangeType>Set</ChangeType> +			</Event> +			*/		 +			// TODO: Question: Do we need to process this at all? +		} +		else if (!stricmp(eventTypeCstr, "MessageEvent"))   +		{ +			gVoiceClient->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); +		} +		else if (!stricmp(eventTypeCstr, "SessionNotificationEvent"))   +		{ +			gVoiceClient->sessionNotificationEvent(sessionHandle, uriString, notificationType); +		} +		else if (!stricmp(eventTypeCstr, "SubscriptionEvent"))   +		{ +			gVoiceClient->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType); +		} +		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 +		{ +			LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; +		}  	}  	else  	{ -		if (actionString == "Connector.Create.1") +		const char *actionCstr = actionString.c_str(); +		if (!stricmp(actionCstr, "Connector.Create.1"))  		{ -			gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle); +			gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID);  		} -		else if (actionString == "Account.Login.1") +		else if (!stricmp(actionCstr, "Account.Login.1"))  		{ -			gVoiceClient->loginResponse(statusCode, statusString, accountHandle); +			gVoiceClient->loginResponse(statusCode, statusString, accountHandle, numberOfAliases);  		} -		else if (actionString == "Session.Create.1") +		else if (!stricmp(actionCstr, "Session.Create.1"))  		{ -			gVoiceClient->sessionCreateResponse(statusCode, statusString, sessionHandle);			 +			gVoiceClient->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle);			  		} -		else if (actionString == "Session.Connect.1") +		else if (!stricmp(actionCstr, "SessionGroup.AddSession.1"))  		{ -			gVoiceClient->sessionConnectResponse(statusCode, statusString);			 +			gVoiceClient->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle);			  		} -		else if (actionString == "Session.Terminate.1") +		else if (!stricmp(actionCstr, "Session.Connect.1"))  		{ -			gVoiceClient->sessionTerminateResponse(statusCode, statusString);			 +			gVoiceClient->sessionConnectResponse(requestId, statusCode, statusString);			  		} -		else if (actionString == "Account.Logout.1") +		else if (!stricmp(actionCstr, "Account.Logout.1"))  		{  			gVoiceClient->logoutResponse(statusCode, statusString);			  		} -		else if (actionString == "Connector.InitiateShutdown.1") +		else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1"))  		{  			gVoiceClient->connectorShutdownResponse(statusCode, statusString);			  		} -		else if (actionString == "Account.ChannelGetList.1") +		else if (!stricmp(actionCstr, "Account.ListBlockRules.1"))  		{ -			gVoiceClient->channelGetListResponse(statusCode, statusString); +			gVoiceClient->accountListBlockRulesResponse(statusCode, statusString);						 +		} +		else if (!stricmp(actionCstr, "Account.ListAutoAcceptRules.1")) +		{ +			gVoiceClient->accountListAutoAcceptRulesResponse(statusCode, statusString);						 +		} +		else if (!stricmp(actionCstr, "Session.Set3DPosition.1")) +		{ +			// We don't need to process these, but they're so spammy we don't want to log them. +			squelchDebugOutput = true;  		}  /* -		else if (actionString == "Connector.AccountCreate.1") +		else if (!stricmp(actionCstr, "Account.ChannelGetList.1"))  		{ -			 +			gVoiceClient->channelGetListResponse(statusCode, statusString);  		} -		else if (actionString == "Connector.MuteLocalMic.1") +		else if (!stricmp(actionCstr, "Connector.AccountCreate.1"))  		{  		} -		else if (actionString == "Connector.MuteLocalSpeaker.1") +		else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1"))  		{  		} -		else if (actionString == "Connector.SetLocalMicVolume.1") +		else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1"))  		{  		} -		else if (actionString == "Connector.SetLocalSpeakerVolume.1") +		else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1"))  		{  		} -		else if (actionString == "Session.ListenerSetPosition.1") +		else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1"))  		{  		} -		else if (actionString == "Session.SpeakerSetPosition.1") +		else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1"))  		{  		} -		else if (actionString == "Session.Set3DPosition.1") +		else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1"))  		{  		} -		else if (actionString == "Session.AudioSourceSetPosition.1") +		else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1"))  		{  		} -		else if (actionString == "Session.GetChannelParticipants.1") +		else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1"))  		{  		} -		else if (actionString == "Account.ChannelCreate.1") +		else if (!stricmp(actionCstr, "Account.ChannelCreate.1"))  		{  		} -		else if (actionString == "Account.ChannelUpdate.1") +		else if (!stricmp(actionCstr, "Account.ChannelUpdate.1"))  		{  		} -		else if (actionString == "Account.ChannelDelete.1") +		else if (!stricmp(actionCstr, "Account.ChannelDelete.1"))  		{  		} -		else if (actionString == "Account.ChannelCreateAndInvite.1") +		else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1"))  		{  		} -		else if (actionString == "Account.ChannelFolderCreate.1") +		else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1"))  		{  		} -		else if (actionString == "Account.ChannelFolderUpdate.1") +		else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1"))  		{  		} -		else if (actionString == "Account.ChannelFolderDelete.1") +		else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1"))  		{  		} -		else if (actionString == "Account.ChannelAddModerator.1") +		else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1"))  		{  		} -		else if (actionString == "Account.ChannelDeleteModerator.1") +		else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1"))  		{  		} @@ -701,9 +980,18 @@ class LLVoiceClientMuteListObserver : public LLMuteListObserver  {  	/* virtual */ void onChange()  { gVoiceClient->muteListChanged();}  }; + +class LLVoiceClientFriendsObserver : public LLFriendObserver +{ +public: +	/* virtual */ void changed(U32 mask) { gVoiceClient->updateFriends(mask);} +}; +  static LLVoiceClientMuteListObserver mutelist_listener;  static bool sMuteListListener_listening = false; +static LLVoiceClientFriendsObserver *friendslist_listener = NULL; +  ///////////////////////////////////////////////////////////////////////////////////////////////  class LLVoiceClientCapResponder : public LLHTTPClient::Responder @@ -727,11 +1015,8 @@ void LLVoiceClientCapResponder::error(U32 status, const std::string& reason)  void LLVoiceClientCapResponder::result(const LLSD& content)  {  	LLSD::map_const_iterator iter; -	for(iter = content.beginMap(); iter != content.endMap(); ++iter) -	{ -		LL_DEBUGS("Voice") << "LLVoiceClientCapResponder::result got "  -			<< iter->first << LL_ENDL; -	} +	 +	LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;  	if ( content.has("voice_credentials") )  	{ @@ -817,26 +1102,26 @@ LLVoiceClient::LLVoiceClient()  	mUserPTTState = false;  	mMuteMic = false;  	mSessionTerminateRequested = false; +	mRelogRequested = false;  	mCommandCookie = 0; -	mNonSpatialChannel = false; -	mNextSessionSpatial = true; -	mNextSessionNoReconnect = false; -	mSessionP2P = false;  	mCurrentParcelLocalID = 0;  	mLoginRetryCount = 0; -	mVivoxErrorStatusCode = 0; -	mNextSessionResetOnClose = false; -	mSessionResetOnClose = false;  	mSpeakerVolume = 0;  	mMicVolume = 0; +	mAudioSession = NULL; +	mAudioSessionChanged = false; +  	// Initial dirty state  	mSpatialCoordsDirty = false;  	mPTTDirty = true; -	mVolumeDirty = true; +	mFriendsListDirty = true;  	mSpeakerVolumeDirty = true;  	mMicVolumeDirty = true; +	mBuddyListMapPopulated = false; +	mBlockRulesListReceived = false; +	mAutoAcceptRulesListReceived = false;  	mCaptureDeviceDirty = false;  	mRenderDeviceDirty = false; @@ -857,14 +1142,12 @@ LLVoiceClient::LLVoiceClient()  	//  gMuteListp isn't set up at this point, so we defer this until later.  //	gMuteListp->addObserver(&mutelist_listener); -	mParticipantMapChanged = false; -  	// stash the pump for later use  	// This now happens when init() is called instead.  	mPump = NULL;  #if LL_DARWIN || LL_LINUX || LL_SOLARIS -		// MBW -- XXX -- THIS DOES NOT BELONG HERE +		// 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? @@ -900,8 +1183,10 @@ void LLVoiceClient::terminate()  {  	if(gVoiceClient)  	{ -		gVoiceClient->sessionTerminateSendMessage(); +//		gVoiceClient->leaveAudioSession();  		gVoiceClient->logout(); +		// As of SDK version 4885, this should no longer be necessary.  It will linger after the socket close if it needs to. +		// ms_sleep(2000);  		gVoiceClient->connectorShutdown();  		gVoiceClient->closeSocket();		// Need to do this now -- bad things happen if the destructor does it later. @@ -926,8 +1211,6 @@ void LLVoiceClient::updateSettings()  	setPTTKey(keyString);  	setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle"));  	setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); -	std::string serverName = gSavedSettings.getString("VivoxDebugServerName"); -	setVivoxDebugServerName(serverName);  	std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");  	setCaptureDevice(inputDevice); @@ -950,9 +1233,10 @@ bool LLVoiceClient::writeString(const std::string &str)  		apr_size_t size = (apr_size_t)str.size();  		apr_size_t written = size; -		LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; +		//MARK: Turn this on to log outgoing XML +//		LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; -		// MBW -- XXX -- check return code - sockets will fail (broken, etc.) +		// check return code - sockets will fail (broken, etc.)  		err = apr_socket_send(  				mSocket->getSocket(),  				(const char*)str.data(), @@ -963,7 +1247,7 @@ bool LLVoiceClient::writeString(const std::string &str)  			// Success.  			result = true;  		} -		// MBW -- XXX -- handle partial writes (written is number of bytes written) +		// TODO: handle partial writes (written is number of bytes written)  		// Need to set socket to non-blocking before this will work.  //		else if(APR_STATUS_IS_EAGAIN(err))  //		{ @@ -987,32 +1271,32 @@ bool LLVoiceClient::writeString(const std::string &str)  void LLVoiceClient::connectorCreate()  {  	std::ostringstream stream; -	std::string logpath; +	std::string logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");  	std::string loglevel = "0";  	// Transition to stateConnectorStarted when the connector handle comes back.  	setState(stateConnectorStarting);  	std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel"); -	 +		  	if(savedLogLevel != "-1")  	{  		LL_DEBUGS("Voice") << "creating connector with logging enabled" << LL_ENDL;  		loglevel = "10"; -		logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");  	}  	stream   	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">"  		<< "<ClientName>V2 SDK</ClientName>" -		<< "<AccountManagementServer>" << mAccountServerURI << "</AccountManagementServer>" +		<< "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" +		<< "<Mode>Normal</Mode>"  		<< "<Logging>" -			<< "<Enabled>false</Enabled>"  			<< "<Folder>" << logpath << "</Folder>"  			<< "<FileNamePrefix>Connector</FileNamePrefix>"  			<< "<FileNameSuffix>.log</FileNameSuffix>"  			<< "<LogLevel>" << loglevel << "</LogLevel>"  		<< "</Logging>" +		<< "<Application>SecondLifeViewer.1</Application>"  	<< "</Request>\n\n\n";  	writeString(stream.str()); @@ -1050,20 +1334,6 @@ void LLVoiceClient::userAuthorized(const std::string& firstName, const std::stri  	sConnectingToAgni = LLViewerLogin::getInstance()->isInProductionGrid(); -	// MBW -- XXX -- Enable this when the bhd.vivox.com server gets a real ssl cert.	 -	if(sConnectingToAgni) -	{ -		// Use the release account server -		mAccountServerName = "bhr.vivox.com"; -		mAccountServerURI = "https://www." + mAccountServerName + "/api2/"; -	} -	else -	{ -		// Use the development account server -		mAccountServerName = gSavedSettings.getString("VivoxDebugServerName"); -		mAccountServerURI = "https://www." + mAccountServerName + "/api2/"; -	} -  	mAccountName = nameFromID(agentID);  } @@ -1085,24 +1355,69 @@ void LLVoiceClient::requestVoiceAccountProvision(S32 retries)  }  void LLVoiceClient::login( -	const std::string& accountName, -	const std::string &password) +	const std::string& account_name, +	const std::string& password, +	const std::string& voice_sip_uri_hostname, +	const std::string& voice_account_server_uri)  { -	if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut)) +	mVoiceSIPURIHostName = voice_sip_uri_hostname; +	mVoiceAccountServerURI = voice_account_server_uri; + +	if(!mAccountHandle.empty())  	{ -		// Already logged in.  This is an internal error. -		LL_ERRS("Voice") << "Can't login again. Called from wrong state." << LL_ENDL; +		// Already logged in. +		LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; +		 +		// Don't process another login. +		return;  	} -	else if ( accountName != mAccountName ) +	else if ( account_name != mAccountName )  	{  		//TODO: error? -		LL_WARNS("Voice") << "Wrong account name! " << accountName +		LL_WARNS("Voice") << "Wrong account name! " << account_name  				<< " instead of " << mAccountName << LL_ENDL;  	}  	else  	{  		mAccountPassword = password;  	} + +	std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); +	 +	if( !debugSIPURIHostName.empty() ) +	{ +		mVoiceSIPURIHostName = debugSIPURIHostName; +	} +	 +	if( mVoiceSIPURIHostName.empty() ) +	{ +		// we have an empty account server name +		// so we fall back to hardcoded defaults + +		if(sConnectingToAgni) +		{ +			// Use the release account server +			mVoiceSIPURIHostName = "bhr.vivox.com"; +		} +		else +		{ +			// Use the development account server +			mVoiceSIPURIHostName = "bhd.vivox.com"; +		} +	} +	 +	std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); + +	if( !debugAccountServerURI.empty() ) +	{ +		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/";		 +	}  }  void LLVoiceClient::idle(void* user_data) @@ -1120,11 +1435,16 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)  	switch(inState)  	{ +		CASE(stateDisableCleanup);  		CASE(stateDisabled);  		CASE(stateStart);  		CASE(stateDaemonLaunched);  		CASE(stateConnecting); +		CASE(stateConnected);  		CASE(stateIdle); +		CASE(stateMicTuningStart); +		CASE(stateMicTuningRunning); +		CASE(stateMicTuningStop);  		CASE(stateConnectorStart);  		CASE(stateConnectorStarting);  		CASE(stateConnectorStarted); @@ -1133,12 +1453,8 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)  		CASE(stateNeedsLogin);  		CASE(stateLoggingIn);  		CASE(stateLoggedIn); +		CASE(stateCreatingSessionGroup);  		CASE(stateNoChannel); -		CASE(stateMicTuningStart); -		CASE(stateMicTuningRunning); -		CASE(stateMicTuningStop); -		CASE(stateSessionCreate); -		CASE(stateSessionConnect);  		CASE(stateJoiningSession);  		CASE(stateSessionJoined);  		CASE(stateRunning); @@ -1155,7 +1471,6 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)  		CASE(stateJoinSessionFailed);  		CASE(stateJoinSessionFailedWaiting);  		CASE(stateJail); -		CASE(stateMicTuningNoLogin);  	}  #undef CASE @@ -1177,6 +1492,7 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv  		CASE(STATUS_JOINING);  		CASE(STATUS_JOINED);  		CASE(STATUS_LEFT_CHANNEL); +		CASE(STATUS_VOICE_DISABLED);  		CASE(BEGIN_ERROR_STATUS);  		CASE(ERROR_CHANNEL_FULL);  		CASE(ERROR_CHANNEL_LOCKED); @@ -1210,9 +1526,13 @@ void LLVoiceClient::stateMachine()  	{  		updatePosition();  	} +	else if(mTuningMode) +	{ +		// Tuning mode is special -- it needs to launch SLVoice even if voice is disabled. +	}  	else  	{ -		if(getState() != stateDisabled) +		if((getState() != stateDisabled) && (getState() != stateDisableCleanup))  		{  			// User turned off voice support.  Send the cleanup messages, close the socket, and reset.  			if(!mConnected) @@ -1222,13 +1542,10 @@ void LLVoiceClient::stateMachine()  				killGateway();  			} -			sessionTerminateSendMessage();  			logout();  			connectorShutdown(); -			closeSocket(); -			removeAllParticipants(); - -			setState(stateDisabled); +			 +			setState(stateDisableCleanup);  		}  	} @@ -1243,7 +1560,7 @@ void LLVoiceClient::stateMachine()  			std::string regionName = region->getName();  			std::string capURI = region->getCapability("ParcelVoiceInfoRequest"); -			LL_DEBUGS("Voice") << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << LL_ENDL; +//			LL_DEBUGS("Voice") << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << 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. @@ -1264,13 +1581,30 @@ void LLVoiceClient::stateMachine()  	switch(getState())  	{ +		//MARK: stateDisableCleanup +		case stateDisableCleanup: +			// Clean up and reset everything.  +			closeSocket(); +			deleteAllSessions(); +			deleteAllBuddies();		 +			 +			mConnectorHandle.clear(); +			mAccountHandle.clear(); +			mAccountPassword.clear(); +			mVoiceAccountServerURI.clear(); +			 +			setState(stateDisabled);	 +		break; +		 +		//MARK: stateDisabled  		case stateDisabled: -			if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode)) +			if(mTuningMode || (mVoiceEnabled && !mAccountName.empty()))  			{  				setState(stateStart);  			}  		break; +		//MARK: stateStart  		case stateStart:  			if(gSavedSettings.getBOOL("CmdLineDisableVoice"))  			{ @@ -1301,7 +1635,9 @@ void LLVoiceClient::stateMachine()  					if(!LLFile::stat(exe_path, &s))  					{  						// vivox executable exists.  Build the command line and launch the daemon. -						std::string args = " -p tcp -h -c"; +						// SLIM SDK: these arguments are no longer necessary. +//						std::string args = " -p tcp -h -c"; +						std::string args;  						std::string cmd;  						std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); @@ -1386,14 +1722,15 @@ void LLVoiceClient::stateMachine()  					}	  					else  					{ -						LL_INFOS("Voice") << exe_path << "not found." << LL_ENDL; +						LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL;  					}	  				}  				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:44124 +					//  vivox-gw.exe -p tcp -i 0.0.0.0:44125  					// and put that host's IP address here.  					mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost"), gSavedSettings.getU32("VoicePort"));  				} @@ -1405,17 +1742,23 @@ void LLVoiceClient::stateMachine()  				// Dirty the states we'll need to sync with the daemon when it comes up.  				mPTTDirty = 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();  			}  		break; -		 + +		//MARK: stateDaemonLaunched  		case stateDaemonLaunched: -			LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;  			if(mUpdateTimer.hasExpired())  			{ +				LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL; +  				mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);  				if(!mSocket) @@ -1436,6 +1779,7 @@ void LLVoiceClient::stateMachine()  			}  		break; +		//MARK: stateConnecting  		case stateConnecting:  		// Can't do this until we have the pump available.  		if(mPump) @@ -1453,48 +1797,34 @@ void LLVoiceClient::stateMachine()  			mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); -			setState(stateIdle); +			setState(stateConnected);  		}  		break; -		case stateIdle: +		//MARK: stateConnected +		case stateConnected:  			// Initial devices query  			getCaptureDevicesSendMessage();  			getRenderDevicesSendMessage();  			mLoginRetryCount = 0; -			 -			setState(stateConnectorStart); -				 + +			setState(stateIdle);  		break; -		 -		case stateConnectorStart: -			if(!mVoiceEnabled) -			{ -				// We were never logged in.  This will shut down the connector. -				setState(stateLoggedOut); -			} -			else if(!mAccountServerURI.empty()) -			{ -				connectorCreate(); -			} -			else if(mTuningMode) + +		//MARK: stateIdle +		case stateIdle: +			// This is the idle state where we're connected to the daemon but haven't set up a connector yet. +			if(mTuningMode)  			{ -				mTuningExitState = stateConnectorStart; +				mTuningExitState = stateIdle;  				setState(stateMicTuningStart);  			} -		break; -		 -		case stateConnectorStarting:	// waiting for connector handle -			// connectorCreateResponse() will transition from here to stateConnectorStarted. -		break; -		 -		case stateConnectorStarted:		// connector handle received -			if(!mVoiceEnabled) +			else if(!mVoiceEnabled)  			{ -				// We were never logged in.  This will shut down the connector. -				setState(stateLoggedOut); +				// We never started up the connector.  This will shut down the daemon. +				setState(stateConnectorStopped);  			}  			else if(!mAccountName.empty())  			{ @@ -1508,12 +1838,13 @@ void LLVoiceClient::stateMachine()  						{  							requestVoiceAccountProvision();  						} -						setState(stateNeedsLogin); +						setState(stateConnectorStart);  					}  				}  			}  		break; -				 + +		//MARK: stateMicTuningStart  		case stateMicTuningStart:  			if(mUpdateTimer.hasExpired())  			{ @@ -1521,19 +1852,9 @@ void LLVoiceClient::stateMachine()  				{  					// These can't be changed while in tuning mode.  Set them before starting.  					std::ostringstream stream; - -					if(mCaptureDeviceDirty) -					{ -						buildSetCaptureDevice(stream); -					} - -					if(mRenderDeviceDirty) -					{ -						buildSetRenderDevice(stream); -					} - -					mCaptureDeviceDirty = false; -					mRenderDeviceDirty = false; +					 +					buildSetCaptureDevice(stream); +					buildSetRenderDevice(stream);  					if(!stream.str().empty())  					{ @@ -1555,8 +1876,9 @@ void LLVoiceClient::stateMachine()  		break; +		//MARK: stateMicTuningRunning  		case stateMicTuningRunning: -			if(!mTuningMode || !mVoiceEnabled || mSessionTerminateRequested || mCaptureDeviceDirty || mRenderDeviceDirty) +			if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty)  			{  				// All of these conditions make us leave tuning mode.  				setState(stateMicTuningStop); @@ -1596,6 +1918,7 @@ void LLVoiceClient::stateMachine()  			}  		break; +		//MARK: stateMicTuningStop  		case stateMicTuningStop:  		{  			// transition out of mic tuning @@ -1609,7 +1932,40 @@ void LLVoiceClient::stateMachine()  		}  		break; -								 +												 +		//MARK: stateConnectorStart +		case stateConnectorStart: +			if(!mVoiceEnabled) +			{ +				// We were never logged in.  This will shut down the connector. +				setState(stateLoggedOut); +			} +			else if(!mVoiceAccountServerURI.empty()) +			{ +				connectorCreate(); +			} +		break; +		 +		//MARK: stateConnectorStarting +		case stateConnectorStarting:	// waiting for connector handle +			// connectorCreateResponse() will transition from here to stateConnectorStarted. +		break; +		 +		//MARK: stateConnectorStarted +		case stateConnectorStarted:		// connector handle received +			if(!mVoiceEnabled) +			{ +				// We were never logged in.  This will shut down the connector. +				setState(stateLoggedOut); +			} +			else +			{ +				// The connector is started.  Send a login message. +				setState(stateNeedsLogin); +			} +		break; +				 +		//MARK: stateLoginRetry  		case stateLoginRetry:  			if(mLoginRetryCount == 0)  			{ @@ -1633,6 +1989,7 @@ void LLVoiceClient::stateMachine()  			}  		break; +		//MARK: stateLoginRetryWait  		case stateLoginRetryWait:  			if(mUpdateTimer.hasExpired())  			{ @@ -1640,6 +1997,7 @@ void LLVoiceClient::stateMachine()  			}  		break; +		//MARK: stateNeedsLogin  		case stateNeedsLogin:  			if(!mAccountPassword.empty())  			{ @@ -1648,16 +2006,22 @@ void LLVoiceClient::stateMachine()  			}		  		break; +		//MARK: stateLoggingIn  		case stateLoggingIn:			// waiting for account handle  			// loginResponse() will transition from here to stateLoggedIn.  		break; +		//MARK: stateLoggedIn  		case stateLoggedIn:				// account handle received -			// Initial kick-off of channel lookup logic -			parcelChanged();  			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); +			// request the current set of block rules (we'll need them when updating the friends list) +			accountListBlockRulesSendMessage(); +			 +			// request the current set of auto-accept rules +			accountListAutoAcceptRulesSendMessage(); +			  			// Set up the mute list observer if it hasn't been set up already.  			if((!sMuteListListener_listening))  			{ @@ -1665,13 +2029,67 @@ void LLVoiceClient::stateMachine()  				sMuteListListener_listening = true;  			} +			// Set up the friends list observer if it hasn't been set up already. +			if(friendslist_listener == NULL) +			{ +				friendslist_listener = new LLVoiceClientFriendsObserver; +				LLAvatarTracker::instance().addObserver(friendslist_listener); +			} +			 +			// Set the initial state of mic mute, local speaker volume, etc. +			{ +				std::ostringstream stream; +				 +				buildLocalAudioUpdates(stream); +				 +				if(!stream.str().empty()) +				{ +					writeString(stream.str()); +				} +			} +			 +#if USE_SESSION_GROUPS			 +			// create the main session group +			sessionGroupCreateSendMessage(); +			 +			setState(stateCreatingSessionGroup); +#else +			// Not using session groups -- skip the stateCreatingSessionGroup state.  			setState(stateNoChannel); + +			// Initial kick-off of channel lookup logic +			parcelChanged();		 +#endif +		break; +		 +		//MARK: stateCreatingSessionGroup +		case stateCreatingSessionGroup: +			if(mSessionTerminateRequested || !mVoiceEnabled) +			{ +				// TODO: Question: is this the right way out of this state +				setState(stateSessionTerminated); +			} +			else if(!mMainSessionGroupHandle.empty()) +			{ +				setState(stateNoChannel); +				 +				// Start looped recording (needed for "panic button" anti-griefing tool) +				recordingLoopStart(); + +				// Initial kick-off of channel lookup logic +				parcelChanged();		 +			}  		break; +		//MARK: stateNoChannel  		case stateNoChannel: +			// Do this here as well as inside sendPositionalUpdate().   +			// 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. +			sendFriendsListUpdates(); +			  			if(mSessionTerminateRequested || !mVoiceEnabled)  			{ -				// MBW -- XXX -- Is this the right way out of this state? +				// TODO: Question: Is this the right way out of this state?  				setState(stateSessionTerminated);  			}  			else if(mTuningMode) @@ -1679,30 +2097,49 @@ void LLVoiceClient::stateMachine()  				mTuningExitState = stateNoChannel;  				setState(stateMicTuningStart);  			} -			else if(!mNextSessionHandle.empty()) +			else if(sessionNeedsRelog(mNextAudioSession))  			{ -				setState(stateSessionConnect); +				requestRelog(); +				setState(stateSessionTerminated);  			} -			else if(!mNextSessionURI.empty()) +			else if(mNextAudioSession) +			{				 +				sessionState *oldSession = mAudioSession; + +				mAudioSession = mNextAudioSession; +				if(!mAudioSession->mReconnect)	 +				{ +					mNextAudioSession = NULL; +				} +				 +				// The old session may now need to be deleted. +				reapSession(oldSession); +				 +				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); +				setState(stateJoiningSession); +			} +			else if(!mSpatialSessionURI.empty())  			{ -				setState(stateSessionCreate); +				// If we're not headed elsewhere and have a spatial URI, return to spatial. +				switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials);  			}  		break; -		case stateSessionCreate: -			sessionCreateSendMessage(); -			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); -			setState(stateJoiningSession); -		break; -		 -		case stateSessionConnect: -			sessionConnectSendMessage(); -			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); -			setState(stateJoiningSession); -		break; -		 +		//MARK: stateJoiningSession  		case stateJoiningSession:		// waiting for session handle -			// sessionCreateResponse() will transition from here to stateSessionJoined. +			// joinedAudioSession() will transition from here to stateSessionJoined.  			if(!mVoiceEnabled)  			{  				// User bailed out during connect -- jump straight to teardown. @@ -1710,30 +2147,27 @@ void LLVoiceClient::stateMachine()  			}  			else if(mSessionTerminateRequested)  			{ -				if(!mSessionHandle.empty()) +				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(mSessionP2P) +					if(mAudioSession->mIsP2P)  					{ -						sessionTerminateSendMessage(); +						sessionMediaDisconnectSendMessage(mAudioSession);  						setState(stateSessionTerminated);  					}  				}  			}  		break; +		//MARK: stateSessionJoined  		case stateSessionJoined:		// session handle received -			// MBW -- XXX -- It appears that I need to wait for BOTH the Session.Create 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 Session.Create response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. +			// 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. -			if(!mSessionHandle.empty()) +			if(mAudioSession && mAudioSession->mVoiceEnabled)  			{ -				// Events that need to happen when a session is joined could go here. -				// Maybe send initial spatial data? -				notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); -  				// Dirty state that may need to be sync'ed with the daemon.  				mPTTDirty = true;  				mSpeakerVolumeDirty = true; @@ -1744,6 +2178,11 @@ void LLVoiceClient::stateMachine()  				// Start the throttle timer  				mUpdateTimer.start();  				mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + +				// Events that need to happen when a session is joined could go here. +				// Maybe send initial spatial data? +				notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); +  			}  			else if(!mVoiceEnabled)  			{ @@ -1754,21 +2193,20 @@ void LLVoiceClient::stateMachine()  			{  				// 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(mSessionP2P) +				if(mAudioSession && mAudioSession->mIsP2P)  				{ -					sessionTerminateSendMessage(); +					sessionMediaDisconnectSendMessage(mAudioSession);  					setState(stateSessionTerminated);  				}  			}  		break; +		//MARK: stateRunning  		case stateRunning:				// steady state -			// sessionTerminateSendMessage() will transition from here to stateLeavingSession -			  			// Disabling voice or disconnect requested.  			if(!mVoiceEnabled || mSessionTerminateRequested)  			{ -				sessionTerminateSendMessage(); +				leaveAudioSession();  			}  			else  			{ @@ -1801,7 +2239,7 @@ void LLVoiceClient::stateMachine()  					}  				} -				if(mNonSpatialChannel) +				if(!inSpatialChannel())  				{  					// When in a non-spatial channel, never send positional updates.  					mSpatialCoordsDirty = false; @@ -1814,7 +2252,7 @@ void LLVoiceClient::stateMachine()  				// Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast)  				// or every 10hz, whichever is sooner. -				if(mVolumeDirty || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired()) +				if((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())  				{  					mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);  					sendPositionalUpdate(); @@ -1822,25 +2260,38 @@ void LLVoiceClient::stateMachine()  			}  		break; +		//MARK: stateLeavingSession  		case stateLeavingSession:		// waiting for terminate session response  			// The handler for the Session.Terminate response will transition from here to stateSessionTerminated.  		break; +		//MARK: stateSessionTerminated  		case stateSessionTerminated: -			// Always reset the terminate request flag when we get here. -			mSessionTerminateRequested = false; +			// Must do this first, since it uses mAudioSession.  			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); +			 +			if(mAudioSession) +			{ +				sessionState *oldSession = mAudioSession; + +				mAudioSession = NULL; +				// We just notified status observers about this change.  Don't do it again. +				mAudioSessionChanged = false; -			if(mVoiceEnabled) +				// The old session may now need to be deleted. +				reapSession(oldSession); +			} +			else  			{ -				// SPECIAL CASE: if going back to spatial but in a parcel with an empty URI, transfer the non-spatial flag now. -				// This fixes the case where you come out of a group chat in a parcel with voice disabled, and get stuck unable to rejoin spatial chat thereafter. -				if(mNextSessionSpatial && mNextSessionURI.empty()) -				{ -					mNonSpatialChannel = !mNextSessionSpatial; -				} -				 +				LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL; +			} +	 +			// Always reset the terminate request flag when we get here. +			mSessionTerminateRequested = false; + +			if(mVoiceEnabled && !mRelogRequested) +			{				  				// Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state).  				setState(stateNoChannel);  			} @@ -1848,49 +2299,67 @@ void LLVoiceClient::stateMachine()  			{  				// Shutting down voice, continue with disconnecting.  				logout(); +				 +				// The state machine will take it from here +				mRelogRequested = false;  			}  		break; +		//MARK: stateLoggingOut  		case stateLoggingOut:			// waiting for logout response  			// The handler for the Account.Logout response will transition from here to stateLoggedOut.  		break; +		//MARK: stateLoggedOut  		case stateLoggedOut:			// logout response received  			// shut down the connector  			connectorShutdown();  		break; +		//MARK: stateConnectorStopping  		case stateConnectorStopping:	// waiting for connector stop  			// The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped.  		break; +		//MARK: stateConnectorStopped  		case stateConnectorStopped:		// connector stop received -			// Clean up and reset everything.  -			closeSocket(); -			removeAllParticipants(); -			setState(stateDisabled); +			setState(stateDisableCleanup);  		break; +		//MARK: stateConnectorFailed  		case stateConnectorFailed:  			setState(stateConnectorFailedWaiting);  		break; +		//MARK: stateConnectorFailedWaiting  		case stateConnectorFailedWaiting:  		break; +		//MARK: stateLoginFailed  		case stateLoginFailed:  			setState(stateLoginFailedWaiting);  		break; +		//MARK: stateLoginFailedWaiting  		case stateLoginFailedWaiting:  			// No way to recover from these.  Yet.  		break; +		//MARK: stateJoinSessionFailed  		case stateJoinSessionFailed:  			// Transition to error state.  Send out any notifications here. -			LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mVivoxErrorStatusCode << "): " << mVivoxErrorStatusString << LL_ENDL; +			if(mAudioSession) +			{ +				LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL; +			} +			else +			{ +				LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL; +			} +			  			notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);  			setState(stateJoinSessionFailedWaiting);  		break; +		//MARK: stateJoinSessionFailedWaiting  		case stateJoinSessionFailedWaiting:  			// Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message.  			// Region crossings may leave this state and try the join again. @@ -1900,22 +2369,24 @@ void LLVoiceClient::stateMachine()  			}  		break; +		//MARK: stateJail  		case stateJail:  			// We have given up.  Do nothing.  		break; -	        case stateMicTuningNoLogin: -			// *TODO: Implement me. -			LL_WARNS("Voice") << "stateMicTuningNoLogin not handled" << LL_ENDL; -		break;  	} - -	if(mParticipantMapChanged) +	 +	if(mAudioSession && mAudioSession->mParticipantsChanged)  	{ -		mParticipantMapChanged = false; -		notifyObservers(); +		mAudioSession->mParticipantsChanged = false; +		mAudioSessionChanged = true; +	} +	 +	if(mAudioSessionChanged) +	{ +		mAudioSessionChanged = false; +		notifyParticipantObservers();  	} -  }  void LLVoiceClient::closeSocket(void) @@ -1927,12 +2398,19 @@ void LLVoiceClient::closeSocket(void)  void LLVoiceClient::loginSendMessage()  {  	std::ostringstream stream; + +	bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); +  	stream  	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"  		<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"  		<< "<AccountName>" << mAccountName << "</AccountName>"  		<< "<AccountPassword>" << mAccountPassword << "</AccountPassword>"  		<< "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>" +		<< "<EnableBuddiesAndPresence>true</EnableBuddiesAndPresence>" +		<< "<BuddyManagementMode>Application</BuddyManagementMode>" +		<< "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>" +		<< (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"")  	<< "</Request>\n\n\n";  	writeString(stream.str()); @@ -1940,7 +2418,10 @@ void LLVoiceClient::loginSendMessage()  void LLVoiceClient::logout()  { -	mAccountPassword = ""; +	// Ensure that we'll re-request provisioning before logging in again +	mAccountPassword.clear(); +	mVoiceAccountServerURI.clear(); +	  	setState(stateLoggingOut);  	logoutSendMessage();  } @@ -1962,78 +2443,164 @@ void LLVoiceClient::logoutSendMessage()  	}  } -void LLVoiceClient::channelGetListSendMessage() +void LLVoiceClient::accountListBlockRulesSendMessage()  { -	std::ostringstream stream; -	stream -	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ChannelGetList.1\">" -		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" -	<< "</Request>\n\n\n"; +	if(!mAccountHandle.empty()) +	{		 +		std::ostringstream stream; -	writeString(stream.str()); +		LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL; + +		stream +		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListBlockRules.1\">" +			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +		<< "</Request>" +		<< "\n\n\n"; + +		writeString(stream.str()); +	}  } -void LLVoiceClient::sessionCreateSendMessage() +void LLVoiceClient::accountListAutoAcceptRulesSendMessage()  { -	LL_DEBUGS("Voice") << "requesting join: " << mNextSessionURI << LL_ENDL; +	if(!mAccountHandle.empty()) +	{		 +		std::ostringstream stream; + +		LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL; + +		stream +		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListAutoAcceptRules.1\">" +			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +		<< "</Request>" +		<< "\n\n\n"; -	mSessionURI = mNextSessionURI; -	mNonSpatialChannel = !mNextSessionSpatial; -	mSessionResetOnClose = mNextSessionResetOnClose; -	mNextSessionResetOnClose = false; -	if(mNextSessionNoReconnect) +		writeString(stream.str()); +	} +} + +void LLVoiceClient::sessionGroupCreateSendMessage() +{ +	if(!mAccountHandle.empty()) +	{		 +		std::ostringstream stream; + +		LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; + +		stream +		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">" +			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +			<< "<Type>Normal</Type>" +		<< "</Request>" +		<< "\n\n\n"; + +		writeString(stream.str()); +	} +} + +void LLVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText) +{ +	LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL; +	 +	session->mCreateInProgress = true; +	if(startAudio)  	{ -		// Clear the stashed URI so it can't reconnect -		mNextSessionURI.clear(); +		session->mMediaConnectInProgress = true;  	} -	// Only p2p sessions are created with "no reconnect". -	mSessionP2P = mNextSessionNoReconnect;  	std::ostringstream stream;  	stream -	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Create.1\">" +	<< "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">"  		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" -		<< "<URI>" << mSessionURI << "</URI>"; +		<< "<URI>" << session->mSIPURI << "</URI>";  	static const std::string allowed_chars =  				"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"  				"0123456789"  				"-._~"; -	if(!mNextSessionHash.empty()) +	if(!session->mHash.empty())  	{  		stream -			<< "<Password>" << LLURI::escape(mNextSessionHash, allowed_chars) << "</Password>" +			<< "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"  			<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";  	}  	stream +		<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" +		<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"  		<< "<Name>" << mChannelName << "</Name>"  	<< "</Request>\n\n\n";  	writeString(stream.str());  } -void LLVoiceClient::sessionConnectSendMessage() +void LLVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)  { -	LL_DEBUGS("Voice") << "connecting to session handle: " << mNextSessionHandle << LL_ENDL; +	LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL; +	 +	session->mCreateInProgress = true; +	if(startAudio) +	{ +		session->mMediaConnectInProgress = true; +	} -	mSessionHandle = mNextSessionHandle; -	mSessionURI = mNextP2PSessionURI; -	mNextSessionHandle.clear();		// never want to re-use these. -	mNextP2PSessionURI.clear(); -	mNonSpatialChannel = !mNextSessionSpatial; -	mSessionResetOnClose = mNextSessionResetOnClose; -	mNextSessionResetOnClose = false; -	// Joining by session ID is only used to answer p2p invitations, so we know this is a p2p session. -	mSessionP2P = 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>" +		<< "<Password>" << password << "</Password>" +		<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>" +	<< "</Request>\n\n\n" +	; +	 +	writeString(stream.str()); +} + +void LLVoiceClient::sessionMediaConnectSendMessage(sessionState *session) +{ +	LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << 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>" +		<< "<Media>Audio</Media>" +	<< "</Request>\n\n\n"; + +	writeString(stream.str()); +} + +void LLVoiceClient::sessionTextConnectSendMessage(sessionState *session) +{ +	LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; +	std::ostringstream stream; +  	stream -	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Connect.1\">" -		<< "<SessionHandle>" << mSessionHandle << "</SessionHandle>" -		<< "<AudioMedia>default</AudioMedia>" +	<< "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">" +		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" +		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"  	<< "</Request>\n\n\n"; +  	writeString(stream.str());  } @@ -2042,52 +2609,112 @@ void LLVoiceClient::sessionTerminate()  	mSessionTerminateRequested = true;  } -void LLVoiceClient::sessionTerminateSendMessage() +void LLVoiceClient::requestRelog()  { -	LL_DEBUGS("Voice") << "leaving session: " << mSessionURI << LL_ENDL; +	mSessionTerminateRequested = true; +	mRelogRequested = true; +} -	switch(getState()) + +void LLVoiceClient::leaveAudioSession() +{ +	if(mAudioSession)  	{ -		case stateNoChannel: -			// In this case, we want to pretend the join failed so our state machine doesn't get stuck. -			// Skip the join failed transition state so we don't send out error notifications. -			setState(stateJoinSessionFailedWaiting); -		break; -		case stateJoiningSession: -		case stateSessionJoined: -		case stateRunning: -			if(!mSessionHandle.empty()) -			{ -				sessionTerminateByHandle(mSessionHandle); -				setState(stateLeavingSession); -			} -			else -			{ -				LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;	 +		LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; + +		switch(getState()) +		{ +			case stateNoChannel: +				// In this case, we want to pretend the join failed so our state machine doesn't get stuck. +				// Skip the join failed transition state so we don't send out error notifications. +				setState(stateJoinSessionFailedWaiting); +			break; +			case stateJoiningSession: +			case stateSessionJoined: +			case stateRunning: +				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); +					setState(stateLeavingSession); +				} +				else +				{ +					LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;	 +					setState(stateSessionTerminated); +				} +			break; +			case stateJoinSessionFailed: +			case stateJoinSessionFailedWaiting:  				setState(stateSessionTerminated); -			} -		break; -		case stateJoinSessionFailed: -		case stateJoinSessionFailedWaiting: -			setState(stateSessionTerminated); -		break; -		 -		default: -			LL_WARNS("Voice") << "called from unknown state" << LL_ENDL; -		break; +			break; +			 +			default: +				LL_WARNS("Voice") << "called from unknown state" << LL_ENDL; +			break; +		} +	} +	else +	{ +		LL_WARNS("Voice") << "called with no active session" << LL_ENDL; +		setState(stateSessionTerminated);  	}  } -void LLVoiceClient::sessionTerminateByHandle(std::string &sessionHandle) +void LLVoiceClient::sessionTerminateSendMessage(sessionState *session)  { -	LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << sessionHandle << LL_ENDL;	 -  	std::ostringstream stream; +	 +	LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL;	  	stream  	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" -		<< "<SessionHandle>" << sessionHandle << "</SessionHandle>" -	<< "</Request>" -	<< "\n\n\n"; +		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>" +	<< "</Request>\n\n\n"; +	 +	writeString(stream.str()); +} + +void LLVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session) +{ +	std::ostringstream stream; +	 +	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 LLVoiceClient::sessionTextDisconnectSendMessage(sessionState *session) +{ +	std::ostringstream stream; +	 +	LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL;	 +	stream +	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.TextDisconnect.1\">" +		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" +		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>" +	<< "</Request>\n\n\n";  	writeString(stream.str());  } @@ -2114,14 +2741,12 @@ void LLVoiceClient::getRenderDevicesSendMessage()  void LLVoiceClient::clearCaptureDevices()  { -	// MBW -- XXX -- do something here  	LL_DEBUGS("Voice") << "called" << LL_ENDL;  	mCaptureDevices.clear();  }  void LLVoiceClient::addCaptureDevice(const std::string& name)  { -	// MBW -- XXX -- do something here  	LL_DEBUGS("Voice") << name << LL_ENDL;  	mCaptureDevices.push_back(name); @@ -2153,15 +2778,13 @@ void LLVoiceClient::setCaptureDevice(const std::string& name)  }  void LLVoiceClient::clearRenderDevices() -{ -	// MBW -- XXX -- do something here +{	  	LL_DEBUGS("Voice") << "called" << LL_ENDL;  	mRenderDevices.clear();  }  void LLVoiceClient::addRenderDevice(const std::string& name)  { -	// MBW -- XXX -- do something here  	LL_DEBUGS("Voice") << name << LL_ENDL;  	mRenderDevices.push_back(name);  } @@ -2273,29 +2896,22 @@ void LLVoiceClient::tuningCaptureStopSendMessage()  void LLVoiceClient::tuningSetMicVolume(float volume)  { -	int scaledVolume = ((int)(volume * 100.0f)) - 100; -	if(scaledVolume != mTuningMicVolume) +	int scaled_volume = scale_mic_volume(volume); + +	if(scaled_volume != mTuningMicVolume)  	{ -		mTuningMicVolume = scaledVolume; +		mTuningMicVolume = scaled_volume;  		mTuningMicVolumeDirty = true;  	}  }  void LLVoiceClient::tuningSetSpeakerVolume(float volume)  { -	// incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. -	// Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50 -	 -	volume -= 0.5f;		// offset volume to the range [-0.5 ... 0.5], with 0 at the default. -	int scaledVolume = 24;	// offset scaledVolume by its default level -	if(volume < 0.0f) -		scaledVolume += ((int)(volume * 248.0f));	// (24 - (-100)) * 2 -	else -		scaledVolume += ((int)(volume * 52.0f));	// (50 - 24) * 2 +	int scaled_volume = scale_speaker_volume(volume);	 -	if(scaledVolume != mTuningSpeakerVolume) +	if(scaled_volume != mTuningSpeakerVolume)  	{ -		mTuningSpeakerVolume = scaledVolume; +		mTuningSpeakerVolume = scaled_volume;  		mTuningSpeakerVolumeDirty = true;  	}  } @@ -2334,67 +2950,193 @@ void LLVoiceClient::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; -	closeSocket(); -	removeAllParticipants(); -	  	// Try to relaunch the daemon -	setState(stateDisabled); +	setState(stateDisableCleanup);  }  void LLVoiceClient::giveUp()  {  	// All has failed.  Clean up and stop trying.  	closeSocket(); -	removeAllParticipants(); +	deleteAllSessions(); +	deleteAllBuddies();  	setState(stateJail);  } +static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) +{ +	F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the  new position and velocity +	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 LLVoiceClient::sendPositionalUpdate(void)  {	  	std::ostringstream stream;  	if(mSpatialCoordsDirty)  	{ -		LLVector3 l, u, a; +		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>" << mSessionHandle << "</SessionHandle>"; +			<< "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>";  		stream << "<SpeakerPosition>"; +//		LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL;  		l = mAvatarRot.getLeftRow();  		u = mAvatarRot.getUpRow();  		a = mAvatarRot.getFwdRow(); -			 -		LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; +		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); +		  		stream   			<< "<Position>" -				<< "<X>" << mAvatarPosition[VX] << "</X>" -				<< "<Y>" << mAvatarPosition[VZ] << "</Y>" -				<< "<Z>" << mAvatarPosition[VY] << "</Z>" +				<< "<X>" << pos.mdV[VX] << "</X>" +				<< "<Y>" << pos.mdV[VY] << "</Y>" +				<< "<Z>" << pos.mdV[VZ] << "</Z>"  			<< "</Position>"  			<< "<Velocity>" -				<< "<X>" << mAvatarVelocity[VX] << "</X>" -				<< "<Y>" << mAvatarVelocity[VZ] << "</Y>" -				<< "<Z>" << mAvatarVelocity[VY] << "</Z>" +				<< "<X>" << vel.mV[VX] << "</X>" +				<< "<Y>" << vel.mV[VY] << "</Y>" +				<< "<Z>" << vel.mV[VZ] << "</Z>"  			<< "</Velocity>"  			<< "<AtOrientation>" -				<< "<X>" << l.mV[VX] << "</X>" -				<< "<Y>" << u.mV[VX] << "</Y>" -				<< "<Z>" << a.mV[VX] << "</Z>" +				<< "<X>" << a.mV[VX] << "</X>" +				<< "<Y>" << a.mV[VY] << "</Y>" +				<< "<Z>" << a.mV[VZ] << "</Z>"  			<< "</AtOrientation>"  			<< "<UpOrientation>" -				<< "<X>" << l.mV[VZ] << "</X>" +				<< "<X>" << u.mV[VX] << "</X>"  				<< "<Y>" << u.mV[VY] << "</Y>" -				<< "<Z>" << a.mV[VZ] << "</Z>" +				<< "<Z>" << u.mV[VZ] << "</Z>"  			<< "</UpOrientation>"  			<< "<LeftOrientation>" -				<< "<X>" << l.mV [VY] << "</X>" -				<< "<Y>" << u.mV [VZ] << "</Y>" -				<< "<Z>" << a.mV [VY] << "</Z>" +				<< "<X>" << l.mV [VX] << "</X>" +				<< "<Y>" << l.mV [VY] << "</Y>" +				<< "<Z>" << l.mV [VZ] << "</Z>"  			<< "</LeftOrientation>";  		stream << "</SpeakerPosition>"; @@ -2430,43 +3172,158 @@ void LLVoiceClient::sendPositionalUpdate(void)  		l = earRot.getLeftRow();  		u = earRot.getUpRow();  		a = earRot.getFwdRow(); +		pos = earPosition; +		vel = earVelocity; -		LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL; - +//		LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL; +		 +		oldSDKTransform(l, u, a, pos, vel); +		  		stream   			<< "<Position>" -				<< "<X>" << earPosition[VX] << "</X>" -				<< "<Y>" << earPosition[VZ] << "</Y>" -				<< "<Z>" << earPosition[VY] << "</Z>" +				<< "<X>" << pos.mdV[VX] << "</X>" +				<< "<Y>" << pos.mdV[VY] << "</Y>" +				<< "<Z>" << pos.mdV[VZ] << "</Z>"  			<< "</Position>"  			<< "<Velocity>" -				<< "<X>" << earVelocity[VX] << "</X>" -				<< "<Y>" << earVelocity[VZ] << "</Y>" -				<< "<Z>" << earVelocity[VY] << "</Z>" +				<< "<X>" << vel.mV[VX] << "</X>" +				<< "<Y>" << vel.mV[VY] << "</Y>" +				<< "<Z>" << vel.mV[VZ] << "</Z>"  			<< "</Velocity>"  			<< "<AtOrientation>" -				<< "<X>" << l.mV[VX] << "</X>" -				<< "<Y>" << u.mV[VX] << "</Y>" -				<< "<Z>" << a.mV[VX] << "</Z>" +				<< "<X>" << a.mV[VX] << "</X>" +				<< "<Y>" << a.mV[VY] << "</Y>" +				<< "<Z>" << a.mV[VZ] << "</Z>"  			<< "</AtOrientation>"  			<< "<UpOrientation>" -				<< "<X>" << l.mV[VZ] << "</X>" +				<< "<X>" << u.mV[VX] << "</X>"  				<< "<Y>" << u.mV[VY] << "</Y>" -				<< "<Z>" << a.mV[VZ] << "</Z>" +				<< "<Z>" << u.mV[VZ] << "</Z>"  			<< "</UpOrientation>"  			<< "<LeftOrientation>" -				<< "<X>" << l.mV [VY] << "</X>" -				<< "<Y>" << u.mV [VZ] << "</Y>" -				<< "<Z>" << a.mV [VY] << "</Z>" +				<< "<X>" << l.mV [VX] << "</X>" +				<< "<Y>" << l.mV [VY] << "</Y>" +				<< "<Z>" << l.mV [VZ] << "</Z>"  			<< "</LeftOrientation>"; +  		stream << "</ListenerPosition>";  		stream << "</Request>\n\n\n";  	}	 +	 +	if(mAudioSession && mAudioSession->mVolumeDirty) +	{ +		participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); + +		mAudioSession->mVolumeDirty = false; +		 +		for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) +		{ +			participantState *p = iter->second; +			 +			if(p->mVolumeDirty) +			{ +				// Can't set volume/mute for yourself +				if(!p->mIsSelf) +				{ +					int volume = p->mUserVolume; +					bool mute = p->mOnMuteList; +										 +					// SLIM SDK: scale volume from 0-400 (with 100 as "normal") to 0-100 (with 56 as "normal") +					if(volume < 100) +						volume = (volume * 56) / 100; +					else +						volume = (((volume - 100) * (100 - 56)) / 300) + 56; + +					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; +					} +					 +					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"; + +					// Send a "mute for me" command for the user +					stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">" +						<< "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" +						<< "<ParticipantURI>" << p->mURI << "</ParticipantURI>" +						<< "<Mute>" << (mute?"1":"0") << "</Mute>" +						<< "</Request>\n\n\n"; +				} +				 +				p->mVolumeDirty = false; +			} +		} +	} +			 +	buildLocalAudioUpdates(stream); +	 +	if(!stream.str().empty()) +	{ +		writeString(stream.str()); +	} +	 +	// Friends list updates can be huge, especially on the first voice login of an account with lots of friends. +	// Batching them all together can choke SLVoice, so send them in separate writes. +	sendFriendsListUpdates(); +} + +void LLVoiceClient::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 LLVoiceClient::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 LLVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream) +{ +	buildSetCaptureDevice(stream); + +	buildSetRenderDevice(stream);  	if(mPTTDirty)  	{ +		mPTTDirty = false; +  		// Send a local mute command.  		// NOTE that the state of "PTT" is the inverse of "local mute".  		//   (i.e. when PTT is true, we send a mute command with "false", and vice versa) @@ -2477,119 +3334,337 @@ void LLVoiceClient::sendPositionalUpdate(void)  			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"  			<< "<Value>" << (mPTT?"false":"true") << "</Value>"  			<< "</Request>\n\n\n"; - -	} -	 -	if(mVolumeDirty) -	{ -		participantMap::iterator iter = mParticipantMap.begin(); -		for(; iter != mParticipantMap.end(); iter++) -		{ -			participantState *p = iter->second; -			 -			if(p->mVolumeDirty) -			{ -				int volume = p->mOnMuteList?0:p->mUserVolume; -				 -				LL_INFOS("Voice") << "Setting volume for avatar " << p->mAvatarID << " to " << volume << LL_ENDL; -				 -				// Send a mute/unumte command for the user (actually "volume for me"). -				stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">" -					<< "<SessionHandle>" << mSessionHandle << "</SessionHandle>" -					<< "<ParticipantURI>" << p->mURI << "</ParticipantURI>" -					<< "<Volume>" << volume << "</Volume>" -					<< "</Request>\n\n\n"; - -				p->mVolumeDirty = false; -			} -		}  	} -	 +  	if(mSpeakerMuteDirty)  	{ -		const char *muteval = ((mSpeakerVolume == -100)?"true":"false"); +		const char *muteval = ((mSpeakerVolume == 0)?"true":"false"); + +		mSpeakerMuteDirty = false; +  		LL_INFOS("Voice") << "Setting speaker mute to " << muteval  << LL_ENDL;  		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">"  			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"  			<< "<Value>" << muteval << "</Value>" -			<< "</Request>\n\n\n";		 +			<< "</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>" << mConnectorHandle << "</ConnectorHandle>"  			<< "<Value>" << mSpeakerVolume << "</Value>" -			<< "</Request>\n\n\n";		 +			<< "</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>" << mConnectorHandle << "</ConnectorHandle>"  			<< "<Value>" << mMicVolume << "</Value>" -			<< "</Request>\n\n\n";		 +			<< "</Request>\n\n\n";				  	} -	// MBW -- XXX -- Maybe check to make sure the capture/render devices are in the current list here? -	if(mCaptureDeviceDirty) -	{ -		buildSetCaptureDevice(stream); -	} +} -	if(mRenderDeviceDirty) +void LLVoiceClient::checkFriend(const LLUUID& id) +{ +	std::string name; +	buddyListEntry *buddy = findBuddy(id); + +	// Make sure we don't add a name before it's been looked up. +	if(gCacheName->getFullName(id, name))  	{ -		buildSetRenderDevice(stream); + +		const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id); +		bool canSeeMeOnline = false; +		if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) +			canSeeMeOnline = true; +		 +		// When we get here, mNeedsSend is true and mInSLFriends is false.  Change them as necessary. +		 +		if(buddy) +		{ +			// This buddy is already in both lists. + +			if(name != buddy->mDisplayName) +			{ +				// The buddy is in the list with the wrong name.  Update it with the correct name. +				LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL; +				buddy->mDisplayName = name; +				buddy->mNeedsNameUpdate = true;		// This will cause the buddy to be resent. +			} +		} +		else +		{ +			// This buddy was not in the vivox list, needs to be added. +			buddy = addBuddy(sipURIFromID(id), name); +			buddy->mUUID = id; +		} +		 +		// In all the above cases, the buddy is in the SL friends list (which is how we got here). +		buddy->mInSLFriends = true; +		buddy->mCanSeeMeOnline = canSeeMeOnline; +		buddy->mNameResolved = true; +		  	} -	 -	mSpatialCoordsDirty = false; -	mPTTDirty = false; -	mVolumeDirty = false; -	mSpeakerVolumeDirty = false; -	mMicVolumeDirty = false; -	mSpeakerMuteDirty = false; -	mCaptureDeviceDirty = false; -	mRenderDeviceDirty = false; -	 -	if(!stream.str().empty()) +	else  	{ -		writeString(stream.str()); +		// This name hasn't been looked up yet.  Don't do anything with this buddy list entry until it has. +		if(buddy) +		{ +			buddy->mNameResolved = false; +		} +		 +		// Initiate a lookup. +		// The "lookup completed" callback will ensure that the friends list is rechecked after it completes. +		lookupName(id);  	}  } -void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) +void LLVoiceClient::clearAllLists()  { -	LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; +	// FOR TESTING ONLY -	stream  -	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">" -		<< "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>" -	<< "</Request>" -	<< "\n\n\n"; +	// This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about. +	buddyListMap::iterator buddy_it; +	for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) +	{ +		buddyListEntry *buddy = buddy_it->second; +		buddy_it++; +		 +		std::ostringstream stream; + +		if(buddy->mInVivoxBuddies) +		{ +			// delete this entry from the vivox buddy list +			buddy->mInVivoxBuddies = false; +			LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; +			stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">" +				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +				<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>" +				<< "</Request>\n\n\n";		 +		} + +		if(buddy->mHasBlockListEntry) +		{ +			// Delete the associated block list entry (so the block list doesn't fill up with junk) +			buddy->mHasBlockListEntry = false; +			stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">" +				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +				<< "<BlockMask>" << buddy->mURI << "</BlockMask>" +				<< "</Request>\n\n\n";								 +		} +		if(buddy->mHasAutoAcceptListEntry) +		{ +			// Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk) +			buddy->mHasAutoAcceptListEntry = false; +			stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">" +				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +				<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>" +				<< "</Request>\n\n\n"; +		} + +		writeString(stream.str()); + +	}  } -void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream) +void LLVoiceClient::sendFriendsListUpdates()  { -	LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; +	if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty) +	{ +		mFriendsListDirty = false; +		 +		if(0) +		{ +			// FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries. +			clearAllLists(); +			return; +		} +		 +		LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL; +		 +		buddyListMap::iterator buddy_it; +		for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) +		{ +			// reset the temp flags in the local buddy list +			buddy_it->second->mInSLFriends = false; +		} +		 +		// correlate with the friends list +		{ +			LLCollectAllBuddies collect; +			LLAvatarTracker::instance().applyFunctor(collect); +			LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin(); +			LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end(); +			 +			for ( ; it != end; ++it) +			{ +				checkFriend(it->second); +			} +			it = collect.mOffline.begin(); +			end = collect.mOffline.end(); +			for ( ; it != end; ++it) +			{ +				checkFriend(it->second); +			} +		} +				 +		LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL; -	stream -	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">" -		<< "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>" -	<< "</Request>" -	<< "\n\n\n"; +		for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) +		{ +			buddyListEntry *buddy = buddy_it->second; +			buddy_it++; +			 +			// Ignore entries that aren't resolved yet. +			if(buddy->mNameResolved) +			{ +				std::ostringstream stream; + +				if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate)) +				{					 +					if(mNumberOfAliases > 0) +					{ +						// Add (or update) this entry in the vivox buddy list +						buddy->mInVivoxBuddies = true; +						buddy->mNeedsNameUpdate = false; +						LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; +						stream  +							<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddySet.1\">" +								<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +								<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>" +								<< "<DisplayName>" << buddy->mDisplayName << "</DisplayName>" +								<< "<BuddyData></BuddyData>"	// Without this, SLVoice doesn't seem to parse the command. +								<< "<GroupID>0</GroupID>" +							<< "</Request>\n\n\n";	 +					} +				} +				else if(!buddy->mInSLFriends) +				{ +					// This entry no longer exists in your SL friends list.  Remove all traces of it from the Vivox buddy list. + 					if(buddy->mInVivoxBuddies) +					{ +						// delete this entry from the vivox buddy list +						buddy->mInVivoxBuddies = false; +						LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; +						stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">" +							<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +							<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>" +							<< "</Request>\n\n\n";		 +					} + +					if(buddy->mHasBlockListEntry) +					{ +						// Delete the associated block list entry, if any +						buddy->mHasBlockListEntry = false; +						stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">" +							<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +							<< "<BlockMask>" << buddy->mURI << "</BlockMask>" +							<< "</Request>\n\n\n";								 +					} +					if(buddy->mHasAutoAcceptListEntry) +					{ +						// Delete the associated auto-accept list entry, if any +						buddy->mHasAutoAcceptListEntry = false; +						stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">" +							<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +							<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>" +							<< "</Request>\n\n\n"; +					} +				} +				 +				if(buddy->mInSLFriends) +				{ + +					if(buddy->mCanSeeMeOnline) +					{ +						// Buddy should not be blocked. + +						// If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent. +						 +						// If the buddy has a block list entry, delete it. +						if(buddy->mHasBlockListEntry) +						{ +							buddy->mHasBlockListEntry = false; +							stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">" +								<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +								<< "<BlockMask>" << buddy->mURI << "</BlockMask>" +								<< "</Request>\n\n\n";		 +							 +							 +							// If we just deleted a block list entry, add an auto-accept entry. +							if(!buddy->mHasAutoAcceptListEntry) +							{ +								buddy->mHasAutoAcceptListEntry = true;								 +								stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateAutoAcceptRule.1\">" +									<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +									<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>" +									<< "<AutoAddAsBuddy>0</AutoAddAsBuddy>" +									<< "</Request>\n\n\n"; +							} +						} +					} +					else +					{ +						// Buddy should be blocked. +						 +						// If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent. + +						// If this buddy has an autoaccept entry, delete it +						if(buddy->mHasAutoAcceptListEntry) +						{ +							buddy->mHasAutoAcceptListEntry = false; +							stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">" +								<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +								<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>" +								<< "</Request>\n\n\n"; +						 +							// If we just deleted an auto-accept entry, add a block list entry. +							if(!buddy->mHasBlockListEntry) +							{ +								buddy->mHasBlockListEntry = true; +								stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateBlockRule.1\">" +									<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +									<< "<BlockMask>" << buddy->mURI << "</BlockMask>" +									<< "<PresenceOnly>1</PresenceOnly>" +									<< "</Request>\n\n\n";								 +							} +						} +					} + +					if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies) +					{ +						// Delete this entry from the local buddy list.  This should NOT invalidate the iterator, +						// since it has already been incremented to the next entry. +						deleteBuddy(buddy->mURI); +					} + +				} +				writeString(stream.str()); +			} +		} +	}  }  /////////////////////////////  // Response/Event handlers -void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle) +void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID)  {	  	if(statusCode != 0)  	{ @@ -2599,6 +3674,7 @@ void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusS  	else  	{  		// Connector created, move forward. +		LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL;  		mConnectorHandle = connectorHandle;  		if(getState() == stateConnectorStarting)  		{ @@ -2607,7 +3683,7 @@ void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusS  	}  } -void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle) +void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases)  {   	LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; @@ -2628,7 +3704,8 @@ void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std  	{  		// Login succeeded, move forward.  		mAccountHandle = accountHandle; -		// MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received. +		mNumberOfAliases = numberOfAliases; +		// This needs to wait until the AccountLoginStateChangeEvent is received.  //		if(getState() == stateLoggingIn)  //		{  //			setState(stateLoggedIn); @@ -2636,106 +3713,91 @@ void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std  	}  } -void LLVoiceClient::channelGetListResponse(int statusCode, std::string &statusString) -{ +void LLVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) +{	 +	sessionState *session = findSessionBeingCreatedByURI(requestId); +	 +	if(session) +	{ +		session->mCreateInProgress = false; +	} +	  	if(statusCode != 0)  	{ -		LL_WARNS("Voice") << "Account.ChannelGetList response failure: " << statusString << LL_ENDL; -		switchChannel(); +		LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; +		if(session) +		{ +			session->mErrorStatusCode = statusCode;		 +			session->mErrorStatusString = statusString; +			if(session == mAudioSession) +			{ +				setState(stateJoinSessionFailed); +			} +			else +			{ +				reapSession(session); +			} +		}  	}  	else  	{ -		// Got the channel list, try to do a lookup. -		std::string uri = findChannelURI(mChannelName); -		if(uri.empty()) -		{	 -			// Lookup failed, can't join a channel for this area. -			LL_INFOS("Voice") << "failed to map channel name: " << mChannelName << LL_ENDL; -		} -		else +		LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; +		if(session)  		{ -			// We have a sip URL for this area. -			LL_INFOS("Voice") << "mapped channel " << mChannelName << " to URI "<< uri << LL_ENDL; +			setSessionHandle(session, sessionHandle);  		} -		 -		// switchChannel with an empty uri string will do the right thing (leave channel and not rejoin) -		switchChannel(uri);  	}  } -void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle) +void LLVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)  {	 +	sessionState *session = findSessionBeingCreatedByURI(requestId); +	 +	if(session) +	{ +		session->mCreateInProgress = false; +	} +	  	if(statusCode != 0)  	{ -		LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; -//		if(statusCode == 1015) -//		{ -//			if(getState() == stateJoiningSession) -//			{ -//				// this happened during a real join.  Going to sessionTerminated should cause a retry in appropriate cases. -//				LL_WARNS("Voice") << "session handle \"" << sessionHandle << "\", mSessionStateEventHandle \"" << mSessionStateEventHandle << "\""<< LL_ENDL; -//				if(!sessionHandle.empty()) -//				{ -//					// This session is bad.  Terminate it. -//					mSessionHandle = sessionHandle; -//					sessionTerminateByHandle(sessionHandle); -//					setState(stateLeavingSession); -//				} -//				else if(!mSessionStateEventHandle.empty()) -//				{ -//					mSessionHandle = mSessionStateEventHandle; -//					sessionTerminateByHandle(mSessionStateEventHandle); -//					setState(stateLeavingSession); -//				} -//				else -//				{ -//					setState(stateSessionTerminated); -//				} -//			} -//			else -//			{ -//				// We didn't think we were in the middle of a join.  Don't change state. -//				LL_WARNS("Voice") << "Not in stateJoiningSession, ignoring" << LL_ENDL; -//			} -//		} -//		else +		LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; +		if(session)  		{ -			mVivoxErrorStatusCode = statusCode;		 -			mVivoxErrorStatusString = statusString; -			setState(stateJoinSessionFailed); +			session->mErrorStatusCode = statusCode;		 +			session->mErrorStatusString = statusString; +			if(session == mAudioSession) +			{ +				setState(stateJoinSessionFailed); +			} +			else +			{ +				reapSession(session); +			}  		}  	}  	else  	{ -		LL_DEBUGS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; -		if(getState() == stateJoiningSession) -		{ -			// This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early... -			mSessionHandle = sessionHandle; -		} -		else +		LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; +		if(session)  		{ -			// We should never get a session.create response in any state except stateJoiningSession.  Things are out of sync.  Kill this session. -			sessionTerminateByHandle(sessionHandle); +			setSessionHandle(session, sessionHandle);  		}  	}  } -void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString) +void LLVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString)  { +	sessionState *session = findSession(requestId);  	if(statusCode != 0)  	{  		LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; -//		if(statusCode == 1015) -//		{ -//			LL_WARNS("Voice") << "terminating existing session" << LL_ENDL; -//			sessionTerminate(); -//		} -//		else +		if(session)  		{ -			mVivoxErrorStatusCode = statusCode;		 -			mVivoxErrorStatusString = statusString; -			setState(stateJoinSessionFailed); +			session->mMediaConnectInProgress = false; +			session->mErrorStatusCode = statusCode;		 +			session->mErrorStatusString = statusString; +			if(session == mAudioSession) +				setState(stateJoinSessionFailed);  		}  	}  	else @@ -2744,27 +3806,12 @@ void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusSt  	}  } -void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString) -{	 -	if(statusCode != 0) -	{ -		LL_WARNS("Voice") << "Session.Terminate response failure: (" << statusCode << "): " << statusString << LL_ENDL; -		if(getState() == stateLeavingSession) -		{ -			// This is probably "(404): Server reporting Failure. Not a member of this conference." -			// Do this so we don't get stuck. -			setState(stateSessionTerminated); -		} -	} -	 -} -  void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString)  {	  	if(statusCode != 0)  	{  		LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; -		// MBW -- XXX -- Should this ever fail?  do we care if it does? +		// Should this ever fail?  do we care if it does?  	}  	if(getState() == stateLoggingOut) @@ -2778,7 +3825,7 @@ void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statu  	if(statusCode != 0)  	{  		LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; -		// MBW -- XXX -- Should this ever fail?  do we care if it does? +		// Should this ever fail?  do we care if it does?  	}  	mConnected = false; @@ -2789,109 +3836,277 @@ void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statu  	}  } -void LLVoiceClient::sessionStateChangeEvent( +void LLVoiceClient::sessionAddedEvent(  		std::string &uriString,  -		int statusCode,  -		std::string &statusString,  -		std::string &sessionHandle, -		int state,   +		std::string &alias,  +		std::string &sessionHandle,  +		std::string &sessionGroupHandle,   		bool isChannel,  -		std::string &nameString) +		bool incoming, +		std::string &nameString, +		std::string &applicationString)  { -	switch(state) -	{ -		case 4:	// I see this when joining the session -			LL_INFOS("Voice") << "joined session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL; +	sessionState *session = NULL; -			// Session create succeeded, move forward. -			mSessionStateEventHandle = sessionHandle; -			mSessionStateEventURI = uriString; -			if(sessionHandle == mSessionHandle) +	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))  			{ -				// This is the session we're joining. -				if(getState() == stateJoiningSession) -				{ -					setState(stateSessionJoined); -					//RN: the uriString being returned by vivox here is actually your account uri, not the channel -					// you are attempting to join, so ignore it -					//LL_DEBUGS("Voice") << "received URI " << uriString << "(previously " << mSessionURI << ")" << LL_ENDL; -					//mSessionURI = uriString; -				} +				// Normal URI(base64-encoded UUID)   			} -			else if(sessionHandle == mNextSessionHandle) +			else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID))  			{ -//				LL_DEBUGS("Voice") << "received URI " << uriString << ", name " << nameString << " for next session (handle " << mNextSessionHandle << ")" << LL_ENDL; +				// 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_WARNS("Voice") << "joining unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL; -				// MBW -- XXX -- Should we send a Session.Terminate here? +				LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; +				setUUIDFromStringHash(session->mCallerID, 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);  			} -			 -		break; -		case 5:	// I see this when leaving the session -			LL_INFOS("Voice") << "left session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL; +		 +			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 LLVoiceClient::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 LLVoiceClient::joinedAudioSession(sessionState *session) +{ +	if(mAudioSession != session) +	{ +		sessionState *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(getState() == stateJoiningSession) +	{ +		setState(stateSessionJoined); +		 +		// SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now. +		// Add the current user as a participant here. +		participantState *participant = session->addParticipant(sipURIFromName(mAccountName)); +		if(participant) +		{ +			participant->mIsSelf = true; +			lookupName(participant->mAvatarID); -			// Set the session handle to the empty string.  If we get back to stateJoiningSession, we'll want to wait for the new session handle. -			if(sessionHandle == mSessionHandle) +			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. +			participantState *participant = session->addParticipant(session->mSIPURI); +			if(participant)  			{ -				// MBW -- XXX -- I think this is no longer necessary, now that we've got mNextSessionURI/mNextSessionHandle -				// mSessionURI.clear(); -				// clear the session handle here just for sanity. -				mSessionHandle.clear(); -				if(mSessionResetOnClose) +				if(participant->mAvatarIDValid)  				{ -					mSessionResetOnClose = false; -					mNonSpatialChannel = false; -					mNextSessionSpatial = true; -					parcelChanged(); +					lookupName(participant->mAvatarID); +				} +				else if(!session->mName.empty()) +				{ +					participant->mDisplayName = session->mName; +					avatarNameResolved(participant->mAvatarID, session->mName);  				} -			 -				removeAllParticipants(); -				switch(getState()) +				// 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 LLVoiceClient::sessionRemovedEvent( +	std::string &sessionHandle,  +	std::string &sessionGroupHandle) +{ +	LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; +	 +	sessionState *session = findSession(sessionHandle); +	if(session) +	{ +		leftAudioSession(session); + +		// This message invalidates the session's handle.  Set it to empty. +		setSessionHandle(session); +		 +		// Reset the media state (we now have no info) +		session->mMediaStreamState = streamStateUnknown; +		session->mTextStreamState = streamStateUnknown; +		 +		// Conditionally delete the session +		reapSession(session); +	} +	else +	{ +		LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; +	} +} + +void LLVoiceClient::reapSession(sessionState *session) +{ +	if(session) +	{ +		if(!session->mHandle.empty()) +		{ +			LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL; +		} +		else 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 +		{ +			// TODO: Question: Should we check for queued text messages here? +			// 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); +			session = NULL; +		}	 +	} +	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 LLVoiceClient::sessionNeedsRelog(sessionState *session) +{ +	bool result = false; +	 +	if(session != NULL) +	{ +		// 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()))  				{ -					case stateJoiningSession: -					case stateSessionJoined: -					case stateRunning: -					case stateLeavingSession: -					case stateJoinSessionFailed: -					case stateJoinSessionFailedWaiting: -						// normal transition -						LL_INFOS("Voice") << "left session " << sessionHandle << "in state " << state2string(getState()) << LL_ENDL; -						setState(stateSessionTerminated); -					break; +					// The hostname in this URI is different from what we expect.  This probably means we need to relog. -					case stateSessionTerminated: -						// this will happen sometimes -- there are cases where we send the terminate and then go straight to this state. -						LL_WARNS("Voice") << "left session " << sessionHandle << "in state " << state2string(getState()) << LL_ENDL; -					break; +					// 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. -					default: -						LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL; -						setState(stateSessionTerminated); -					break; +					result = true;  				} - -				// store status values for later notification of observers -				mVivoxErrorStatusCode = statusCode;		 -				mVivoxErrorStatusString = statusString; -			} -			else -			{ -				LL_INFOS("Voice") << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL;  			} +		} +	} +	 +	return result; +} -			mSessionStateEventHandle.clear(); -			mSessionStateEventURI.clear(); -		break; -		default: -			LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL; -		break; +void LLVoiceClient::leftAudioSession( +	sessionState *session) +{ +	if(mAudioSession == session) +	{ +		switch(getState()) +		{ +			case stateJoiningSession: +			case stateSessionJoined: +			case stateRunning: +			case stateLeavingSession: +			case stateJoinSessionFailed: +			case stateJoinSessionFailedWaiting: +				// normal transition +				LL_DEBUGS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL; +				setState(stateSessionTerminated); +			break; +			 +			case stateSessionTerminated: +				// this will happen sometimes -- there are cases where we send the terminate and then go straight to this state. +				LL_WARNS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL; +			break; +			 +			default: +				LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL; +				setState(stateSessionTerminated); +			break; +		}  	}  } -void LLVoiceClient::loginStateChangeEvent( +void LLVoiceClient::accountLoginStateChangeEvent(  		std::string &accountHandle,   		int statusCode,   		std::string &statusString,  @@ -2924,110 +4139,571 @@ void LLVoiceClient::loginStateChangeEvent(  	}  } -void LLVoiceClient::sessionNewEvent( -		std::string &accountHandle,  -		std::string &eventSessionHandle,  -		int state,  -		std::string &nameString,  -		std::string &uriString) +void LLVoiceClient::mediaStreamUpdatedEvent( +	std::string &sessionHandle,  +	std::string &sessionGroupHandle,  +	int statusCode,  +	std::string &statusString,  +	int state,  +	bool incoming)  { -	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; +	sessionState *session = findSession(sessionHandle); -	switch(state) +	LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; +	 +	if(session)  	{ -		case 0: -			{ -				LL_DEBUGS("Voice") << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << LL_ENDL; +		// We know about this session +		 +		// Save the state for later use +		session->mMediaStreamState = state; +		 +		switch(statusCode) +		{ +			case 0: +			case 200: +				// 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 streamStateIdle: +				// Standard "left audio session" +				session->mVoiceEnabled = false; +				session->mMediaConnectInProgress = false; +				leftAudioSession(session); +			break; + +			case streamStateConnected: +				session->mVoiceEnabled = true; +				session->mMediaConnectInProgress = false; +				joinedAudioSession(session); +			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 +	{ +		LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL; +	} +} -				LLUUID caller_id; -				if(IDFromName(nameString, caller_id)) +void LLVoiceClient::textStreamUpdatedEvent( +	std::string &sessionHandle,  +	std::string &sessionGroupHandle,  +	bool enabled, +	int state,  +	bool incoming) +{ +	sessionState *session = findSession(sessionHandle); +	 +	if(session) +	{ +		// Save the state for later use +		session->mTextStreamState = state; +		 +		// We know about this session +		switch(state) +		{ +			case 0:	// We see this when the text stream closes +				LL_DEBUGS("Voice") << "stream closed" << LL_ENDL; +			break; +			 +			case 1:	// We see this on an incoming call from the Connector +				// Try to send any text messages queued for this session. +				sendQueuedTextMessages(session); + +				// Send the text chat invite to the GUI layer +				// TODO: Question: Should we correlate with the mute list here? +				session->mTextInvitePending = true; +				if(session->mName.empty())  				{ -					gIMMgr->inviteToSession( -						LLIMMgr::computeSessionID( -							IM_SESSION_P2P_INVITE, -							caller_id), -						LLStringUtil::null, -						caller_id,  -						LLStringUtil::null,  -						IM_SESSION_P2P_INVITE,  -						LLIMMgr::INVITATION_TYPE_VOICE, -						eventSessionHandle); +					lookupName(session->mCallerID);  				}  				else  				{ -					LL_WARNS("Voice") << "Could not generate caller id from uri " << uriString << LL_ENDL; +					// Act like we just finished resolving the name +					avatarNameResolved(session->mCallerID, session->mName);  				} -			} -		break; -		 -		default: -			LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL; -		break; +			break; + +			default: +				LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; +			break; +			 +		}  	}  } -void LLVoiceClient::participantStateChangeEvent( +void LLVoiceClient::participantAddedEvent( +		std::string &sessionHandle,  +		std::string &sessionGroupHandle,   		std::string &uriString,  -		int statusCode,  -		std::string &statusString,  -		int state,   +		std::string &alias,   		std::string &nameString,   		std::string &displayNameString,   		int participantType)  { -	participantState *participant = NULL; -	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; - -	switch(state) +	sessionState *session = findSession(sessionHandle); +	if(session)  	{ -		case 7:	// I see this when a participant joins -			participant = addParticipant(uriString); -			if(participant) +		participantState *participant = session->addParticipant(uriString); +		if(participant) +		{ +			participant->mAccountName = nameString; + +			LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName  +					<< "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + +			if(participant->mAvatarIDValid)  			{ -				participant->mName = nameString; -				LL_DEBUGS("Voice") << "added participant \"" << participant->mName  -						<< "\" (" << participant->mAvatarID << ")"<< LL_ENDL; +				// Initiate a lookup +				lookupName(participant->mAvatarID);  			} -		break; -		case 9:	// I see this when a participant leaves -			participant = findParticipant(uriString); -			if(participant) +			else  			{ -				removeParticipant(participant); +				// 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);  			} -		break; -		default: -			LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; -		break; +		}  	}  } -void LLVoiceClient::participantPropertiesEvent( +void LLVoiceClient::participantRemovedEvent( +		std::string &sessionHandle,  +		std::string &sessionGroupHandle,   		std::string &uriString,  -		int statusCode,  -		std::string &statusString,  -		bool isLocallyMuted,  +		std::string &alias,  +		std::string &nameString) +{ +	sessionState *session = findSession(sessionHandle); +	if(session) +	{ +		participantState *participant = session->findParticipant(uriString); +		if(participant) +		{ +			session->removeParticipant(participant); +		} +		else +		{ +			LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; +		} +	} +	else +	{ +		LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; +	} +} + + +void LLVoiceClient::participantUpdatedEvent( +		std::string &sessionHandle,  +		std::string &sessionGroupHandle,  +		std::string &uriString,  +		std::string &alias,   		bool isModeratorMuted,   		bool isSpeaking,   		int volume,   		F32 energy)  { -	participantState *participant = findParticipant(uriString); -	if(participant) +	sessionState *session = findSession(sessionHandle); +	if(session)  	{ -		participant->mPTT = !isLocallyMuted; -		participant->mIsSpeaking = isSpeaking; -		participant->mIsModeratorMuted = isModeratorMuted; -		if (isSpeaking) +		participantState *participant = session->findParticipant(uriString); +		 +		if(participant)  		{ -			participant->mSpeakingTimeout.reset(); +			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; +			} +			participant->mVolume = volume; +		} +		else +		{ +			LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;  		} -		participant->mPower = energy; -		participant->mVolume = volume;  	}  	else  	{ -		LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; +		LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL; +	} +} + +void LLVoiceClient::buddyPresenceEvent( +		std::string &uriString,  +		std::string &alias,  +		std::string &statusString, +		std::string &applicationString) +{ +	buddyListEntry *buddy = findBuddy(uriString); +	 +	if(buddy) +	{ +		LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL; +		LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL; + +		if(applicationString.empty()) +		{ +			// This presence event is from a client that doesn't set up the Application string.  Do things the old-skool way. +			// NOTE: this will be needed to support people who aren't on the 3010-class SDK yet. + +			if ( stricmp("Unknown", statusString.c_str())== 0)  +			{ +				// User went offline with a non-SLim-enabled viewer. +				buddy->mOnlineSL = false; +			} +			else if ( stricmp("Online", statusString.c_str())== 0)  +			{ +				// User came online with a non-SLim-enabled viewer. +				buddy->mOnlineSL = true; +			} +			else +			{ +				// If the user is online through SLim, their status will be "Online-slc", "Away", or something else. +				// NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string. +				buddy->mOnlineSLim = true; +			}  +		} +		else if(applicationString.find("SecondLifeViewer") != std::string::npos) +		{ +			// This presence event is from a viewer that sets the application string +			if ( stricmp("Unknown", statusString.c_str())== 0)  +			{ +				// Viewer says they're offline +				buddy->mOnlineSL = false; +			} +			else +			{ +				// Viewer says they're online +				buddy->mOnlineSL = true; +			} +		} +		else +		{ +			// This presence event is from something which is NOT the SL viewer (assume it's SLim). +			if ( stricmp("Unknown", statusString.c_str())== 0)  +			{ +				// SLim says they're offline +				buddy->mOnlineSLim = false; +			} +			else +			{ +				// SLim says they're online +				buddy->mOnlineSLim = true; +			} +		}  + +		LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL; +		 +		// HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change. +		LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID)); + +		notifyFriendObservers(); +	} +	else +	{ +		LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL; +	}	 +} + +void LLVoiceClient::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; +	 +	if(messageHeader.find("text/html") != std::string::npos) +	{ +		std::string rawMessage; + +		{ +			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. +			rawMessage = 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; +					 +				rawMessage.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; +					 +					rawMessage.assign(messageBody, start, end); +				}			 +			} +		}	 +		 +//		LL_DEBUGS("Voice") << "    raw message = \n" << rawMessage << LL_ENDL; + +		// strip formatting tags +		{ +			std::string::size_type start; +			std::string::size_type end; +			 +			while((start = rawMessage.find('<')) != std::string::npos) +			{ +				if((end = rawMessage.find('>', start + 1)) != std::string::npos) +				{ +					// Strip out the tag +					rawMessage.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 = rawMessage.find("<", mark)) != std::string::npos) +			{ +				rawMessage.replace(mark, 4, "<"); +				mark += 1; +			} +			 +			mark = 0; +			while((mark = rawMessage.find(">", mark)) != std::string::npos) +			{ +				rawMessage.replace(mark, 4, ">"); +				mark += 1; +			} +			 +			mark = 0; +			while((mark = rawMessage.find("&", mark)) != std::string::npos) +			{ +				rawMessage.replace(mark, 5, "&"); +				mark += 1; +			} +		} +		 +		// strip leading/trailing whitespace (since we always seem to get a couple newlines) +		LLStringUtil::trim(rawMessage); +		 +//		LL_DEBUGS("Voice") << "    stripped message = \n" << rawMessage << LL_ENDL; +		 +		sessionState *session = findSession(sessionHandle); +		if(session) +		{ +			bool is_busy = gAgent.getBusy(); +			bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); +			bool is_linden = LLMuteList::getInstance()->isLinden(session->mName); +			bool quiet_chat = false; +			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_busy && !is_linden) +				{ +					quiet_chat = true; +					// TODO: Question: Return busy mode response here?  Or maybe when session is started instead? +				} +				 +				std::string fullMessage = std::string(": ") + rawMessage; +				 +				LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; +				gIMMgr->addMessage(session->mIMSessionID, +						session->mCallerID, +						session->mName.c_str(), +						fullMessage.c_str(), +						LLStringUtil::null,		// default arg +						IM_NOTHING_SPECIAL,		// default arg +						0,						// default arg +						LLUUID::null,			// default arg +						LLVector3::zero,		// default arg +						true);					// prepend name and make it a link to the user's profile + +				chat.mText = std::string("IM: ") + session->mName + std::string(": ") + rawMessage; +				// If the chat should come in quietly (i.e. we're in busy mode), pretend it's from a local agent. +				LLFloaterChat::addChat( chat, TRUE, quiet_chat ); +			} +		}		 +	} +} + +void LLVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) +{ +	sessionState *session = findSession(sessionHandle); +	 +	if(session) +	{ +		participantState *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 an LLIMInfo 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 an LLIMInfo 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 LLVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType) +{ +	buddyListEntry *buddy = findBuddy(buddyURI); +	 +	if(!buddy) +	{ +		// Couldn't find buddy by URI, try converting the alias... +		if(!alias.empty()) +		{ +			LLUUID id; +			if(IDFromName(alias, id)) +			{ +				buddy = findBuddy(id); +			} +		} +	} +	 +	if(buddy) +	{ +		std::ostringstream stream; +		 +		if(buddy->mCanSeeMeOnline) +		{ +			// Sending the response will create an auto-accept rule +			buddy->mHasAutoAcceptListEntry = true; +		} +		else +		{ +			// Sending the response will create a block rule +			buddy->mHasBlockListEntry = true; +		} +		 +		if(buddy->mInSLFriends) +		{ +			buddy->mInVivoxBuddies = true; +		} +		 +		stream +			<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.SendSubscriptionReply.1\">" +				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +				<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>" +				<< "<RuleType>" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "</RuleType>" +				<< "<AutoAccept>"<< (buddy->mInSLFriends?"1":"0")<< "</AutoAccept>" +				<< "<SubscriptionHandle>" << subscriptionHandle << "</SubscriptionHandle>" +			<< "</Request>" +			<< "\n\n\n"; +			 +		writeString(stream.str());  	}  } @@ -3037,155 +4713,226 @@ void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)  	mTuningEnergy = energy;  } +void LLVoiceClient::buddyListChanged() +{ +	// This is called after we receive a BuddyAndGroupListChangedEvent. +	mBuddyListMapPopulated = true; +	mFriendsListDirty = true; +} +  void LLVoiceClient::muteListChanged()  {  	// The user's mute list has been updated.  Go through the current participant list and sync it with the mute list. - -	participantMap::iterator iter = mParticipantMap.begin(); -	 -	for(; iter != mParticipantMap.end(); iter++) +	if(mAudioSession)  	{ -		participantState *p = iter->second; +		participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); -		// Check to see if this participant is on the mute list already -		updateMuteState(p); +		for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) +		{ +			participantState *p = iter->second; +			 +			// Check to see if this participant is on the mute list already +			if(p->updateMuteState()) +				mAudioSession->mVolumeDirty = true; +		} +	} +} + +void LLVoiceClient::updateFriends(U32 mask) +{ +	if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::POWERS)) +	{ +		// Just resend the whole friend list to the daemon +		mFriendsListDirty = true;  	}  }  /////////////////////////////  // Managing list of participants  LLVoiceClient::participantState::participantState(const std::string &uri) :  -	 mURI(uri), mPTT(false), mIsSpeaking(false), mIsModeratorMuted(false), mLastSpokeTimestamp(0.f), mPower(0.f), mVolume(0), mServiceType(serviceTypeUnknown), -	 mOnMuteList(false), mUserVolume(100), mVolumeDirty(false), mAvatarIDValid(false) +	 mURI(uri),  +	 mPTT(false),  +	 mIsSpeaking(false),  +	 mIsModeratorMuted(false),  +	 mLastSpokeTimestamp(0.f),  +	 mPower(0.f),  +	 mVolume(0),  +	 mOnMuteList(false),  +	 mUserVolume(100),  +	 mVolumeDirty(false),  +	 mAvatarIDValid(false), +	 mIsSelf(false)  {  } -LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri) +LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(const std::string &uri)  {  	participantState *result = NULL; - -	participantMap::iterator iter = mParticipantMap.find(uri); +	bool useAlternateURI = false; -	if(iter != mParticipantMap.end()) +	// Note: this is mostly the body of LLVoiceClient::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.  	{ -		// Found a matching participant already in the map. -		result = iter->second; -	} +		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 = new participantState(uri); -		mParticipantMap.insert(participantMap::value_type(uri, result)); -		mParticipantMapChanged = true; +		result = 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(IDFromName(uri, id)) +			if(IDFromName(result->mURI, id))  			{  				result->mAvatarIDValid = true;  				result->mAvatarID = id; -				updateMuteState(result); +				if(result->updateMuteState()) +					mVolumeDirty = true; +			} +			else +			{ +				// Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. +				// This tells both code in LLVoiceClient and code in llfloateractivespeakers.cpp that the ID will not be in the name cache. +				setUUIDFromStringHash(result->mAvatarID, uri);  			}  		} +		mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result)); +		  		LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;  	}  	return result;  } -void LLVoiceClient::updateMuteState(participantState *p) +bool LLVoiceClient::participantState::updateMuteState()  { -	if(p->mAvatarIDValid) +	bool result = false; +	 +	if(mAvatarIDValid)  	{ -		bool isMuted = LLMuteList::getInstance()->isMuted(p->mAvatarID, LLMute::flagVoiceChat); -		if(p->mOnMuteList != isMuted) +		bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); +		if(mOnMuteList != isMuted)  		{ -			p->mOnMuteList = isMuted; -			p->mVolumeDirty = true; +			mOnMuteList = isMuted;  			mVolumeDirty = true; +			result = true;  		}  	} +	return result;  } -void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant) +void LLVoiceClient::sessionState::removeParticipant(LLVoiceClient::participantState *participant)  {  	if(participant)  	{ -		participantMap::iterator iter = mParticipantMap.find(participant->mURI); -				 +		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; - -		mParticipantMap.erase(iter); -		delete participant; -		mParticipantMapChanged = true; +		 +		if(iter == mParticipantsByURI.end()) +		{ +			LL_ERRS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; +		} +		else if(iter2 == mParticipantsByUUID.end()) +		{ +			LL_ERRS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; +		} +		else if(iter->second != iter2->second) +		{ +			LL_ERRS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; +		} +		else +		{ +			mParticipantsByURI.erase(iter); +			mParticipantsByUUID.erase(iter2); +			 +			delete participant; +			mParticipantsChanged = true; +		}  	}  } -void LLVoiceClient::removeAllParticipants() +void LLVoiceClient::sessionState::removeAllParticipants()  {  	LL_DEBUGS("Voice") << "called" << LL_ENDL; -	while(!mParticipantMap.empty()) +	while(!mParticipantsByURI.empty())  	{ -		removeParticipant(mParticipantMap.begin()->second); +		removeParticipant(mParticipantsByURI.begin()->second); +	} +	 +	if(!mParticipantsByUUID.empty()) +	{ +		LL_ERRS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL  	}  }  LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)  { -	return &mParticipantMap; +	participantMap *result = NULL; +	if(mAudioSession) +	{ +		result = &(mAudioSession->mParticipantsByURI); +	} +	return result;  } -LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri) +LLVoiceClient::participantState *LLVoiceClient::sessionState::findParticipant(const std::string &uri)  {  	participantState *result = NULL; -	// Don't find any participants if we're not connected.  This is so that we don't continue to get stale data -	// after the daemon dies. -	if(mConnected) +	participantMap::iterator iter = mParticipantsByURI.find(&uri); + +	if(iter == mParticipantsByURI.end())  	{ -		participantMap::iterator iter = mParticipantMap.find(uri); -	 -		if(iter != mParticipantMap.end()) +		if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))  		{ -			result = iter->second; +			// 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;  } - -LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar) +LLVoiceClient::participantState* LLVoiceClient::sessionState::findParticipantByID(const LLUUID& id)  {  	participantState * result = NULL; +	participantUUIDMap::iterator iter = mParticipantsByUUID.find(&id); -	// You'd think this would work, but it doesn't... -//	std::string uri = sipURIFromAvatar(avatar); -	 -	// Currently, the URI is just the account name. -	std::string loginName = nameFromAvatar(avatar); -	result = findParticipant(loginName); -	 -	if(result != NULL) +	if(iter != mParticipantsByUUID.end())  	{ -		if(!result->mAvatarIDValid) -		{ -			result->mAvatarID = avatar->getID(); -			result->mAvatarIDValid = true; -			 -			// We just figured out the avatar ID, so the participant list has "changed" from the perspective of anyone who uses that to identify participants. -			mParticipantMapChanged = true; -			 -			updateMuteState(result); -		} -		 - +		result = iter->second;  	}  	return result; @@ -3194,43 +4941,19 @@ LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvat  LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)  {  	participantState * result = NULL; - -	// Currently, the URI is just the account name. -	std::string loginName = nameFromID(id); -	result = findParticipant(loginName); - -	return result; -} - - -void LLVoiceClient::clearChannelMap(void) -{ -	mChannelMap.clear(); -} - -void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri) -{ -	LL_DEBUGS("Voice") << "Adding channel name mapping: " << name << " -> " << uri << LL_ENDL; -	mChannelMap.insert(channelMap::value_type(name, uri)); -} - -std::string LLVoiceClient::findChannelURI(std::string &name) -{ -	std::string result; -	channelMap::iterator iter = mChannelMap.find(name); - -	if(iter != mChannelMap.end()) +	if(mAudioSession)  	{ -		result = iter->second; +		result = mAudioSession->findParticipantByID(id);  	}  	return result;  } +  void LLVoiceClient::parcelChanged()  { -	if(getState() >= stateLoggedIn) +	if(getState() >= stateNoChannel)  	{  		// If the user is logged in, start a channel lookup.  		LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; @@ -3244,7 +4967,7 @@ void LLVoiceClient::parcelChanged()  	}  	else  	{ -		// The transition to stateLoggedIn needs to kick this off again. +		// The transition to stateNoChannel needs to kick this off again.  		LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL;  	}  } @@ -3252,12 +4975,17 @@ void LLVoiceClient::parcelChanged()  void LLVoiceClient::switchChannel(  	std::string uri,  	bool spatial, -	bool noReconnect, +	bool no_reconnect, +	bool is_p2p,  	std::string hash)  {  	bool needsSwitch = false; -	LL_DEBUGS("Voice") << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << LL_ENDL; +	LL_DEBUGS("Voice")  +		<< "called in state " << state2string(getState())  +		<< " with uri \"" << uri << "\""  +		<< (spatial?", spatial is true":", spatial is false") +		<< LL_ENDL;  	switch(getState())  	{ @@ -3267,41 +4995,73 @@ void LLVoiceClient::switchChannel(  			// Always switch to the new URI from these states.  			needsSwitch = true;  		break; -		 +  		default:  			if(mSessionTerminateRequested)  			{  				// If a terminate has been requested, we need to compare against where the URI we're already headed to. -				if(mNextSessionURI != uri) -					needsSwitch = true; +				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(mSessionURI != uri) -					needsSwitch = true; +				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." << LL_ENDL; +					} +				}  			}  		break;  	}  	if(needsSwitch)  	{ -		mNextSessionURI = uri; -		mNextSessionHash = hash; -		mNextSessionHandle.clear(); -		mNextP2PSessionURI.clear(); -		mNextSessionSpatial = spatial; -		mNextSessionNoReconnect = noReconnect; -		  		if(uri.empty())  		{  			// Leave any channel we may be in  			LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; + +			sessionState *oldSession = mNextAudioSession; +			mNextAudioSession = NULL; + +			// The old session may now need to be deleted. +			reapSession(oldSession); +  			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(getState() <= stateNoChannel) @@ -3316,15 +5076,10 @@ void LLVoiceClient::switchChannel(  	}  } -void LLVoiceClient::joinSession(std::string handle, std::string uri) +void LLVoiceClient::joinSession(sessionState *session)  { -	mNextSessionURI.clear(); -	mNextSessionHash.clear(); -	mNextP2PSessionURI = uri; -	mNextSessionHandle = handle; -	mNextSessionSpatial = false; -	mNextSessionNoReconnect = false; - +	mNextAudioSession = session; +	  	if(getState() <= stateNoChannel)  	{  		// We're already set up to join a channel, just needed to fill in the session handle @@ -3340,7 +5095,7 @@ void LLVoiceClient::setNonSpatialChannel(  	const std::string &uri,  	const std::string &credentials)  { -	switchChannel(uri, false, false, credentials); +	switchChannel(uri, false, false, false, credentials);  }  void LLVoiceClient::setSpatialChannel( @@ -3348,51 +5103,216 @@ void LLVoiceClient::setSpatialChannel(  	const std::string &credentials)  {  	mSpatialSessionURI = uri; +	mSpatialSessionCredentials = credentials;  	mAreaVoiceDisabled = mSpatialSessionURI.empty();  	LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; -	if(mNonSpatialChannel || !mNextSessionSpatial) +	if((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;  	}  	else  	{ -		switchChannel(mSpatialSessionURI, true, false, credentials); +		switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials);  	}  } -void LLVoiceClient::callUser(LLUUID &uuid) +void LLVoiceClient::callUser(const LLUUID &uuid)  {  	std::string userURI = sipURIFromID(uuid); -	switchChannel(userURI, false, true); +	switchChannel(userURI, false, true, true); +} + +LLVoiceClient::sessionState* LLVoiceClient::startUserIMSession(const LLUUID &uuid) +{ +	// Figure out if a session with the user already exists +	sessionState *session = findSession(uuid); +	if(!session) +	{ +		// No session with user, need to start one. +		std::string uri = sipURIFromID(uuid); +		session = addSession(uri); +		session->mIsSpatial = false; +		session->mReconnect = false;	 +		session->mIsP2P = true; +		session->mCallerID = uuid; +	} +	 +	if(session) +	{ +		if(session->mHandle.empty()) +		{ +			// Session isn't active -- start it up. +			sessionCreateSendMessage(session, false, true); +		} +		else +		{	 +			// Session is already active -- start up text. +			sessionTextConnectSendMessage(session); +		} +	} +	 +	return session;  } -void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id) +bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message)  { -	joinSession(sessionHandle, sipURIFromID(other_user_id)); +	bool result = false; + +	// Attempt to locate the indicated session +	sessionState *session = startUserIMSession(participant_id); +	if(session) +	{ +		// found the session, attempt to send the message +		session->mTextMsgQueue.push(message); +		 +		// Try to send queued messages (will do nothing if the session is not open yet) +		sendQueuedTextMessages(session); + +		// The message is queued, so we succeed. +		result = true; +	}	 +	else +	{ +		LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL; +	} +	 +	return result; +} + +void LLVoiceClient::sendQueuedTextMessages(sessionState *session) +{ +	if(session->mTextStreamState == 1) +	{ +		if(!session->mTextMsgQueue.empty()) +		{ +			std::ostringstream stream; +			 +			while(!session->mTextMsgQueue.empty()) +			{ +				std::string message = session->mTextMsgQueue.front(); +				session->mTextMsgQueue.pop(); +				stream +				<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SendMessage.1\">" +					<< "<SessionHandle>" << session->mHandle << "</SessionHandle>" +					<< "<MessageHeader>text/HTML</MessageHeader>" +					<< "<MessageBody>" << message << "</MessageBody>" +				<< "</Request>" +				<< "\n\n\n"; +			}		 +			writeString(stream.str()); +		} +	} +	else +	{ +		// Session isn't connected yet, defer until later. +	} +} + +void LLVoiceClient::endUserIMSession(const LLUUID &uuid) +{ +	// Figure out if a session with the user exists +	sessionState *session = findSession(uuid); +	if(session) +	{ +		// found the session +		if(!session->mHandle.empty()) +		{ +			sessionTextDisconnectSendMessage(session); +		} +	}	 +	else +	{ +		LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; +	} +} + +bool LLVoiceClient::answerInvite(std::string &sessionHandle) +{ +	// this is only ever used to answer incoming p2p call invites. +	 +	sessionState *session = findSession(sessionHandle); +	if(session) +	{ +		session->mIsSpatial = false; +		session->mReconnect = false;	 +		session->mIsP2P = true; + +		joinSession(session); +		return true; +	} +	 +	return false; +} + +bool LLVoiceClient::isOnlineSIP(const LLUUID &id) +{ +	bool result = false; +	buddyListEntry *buddy = findBuddy(id); +	if(buddy) +	{ +		result = buddy->mOnlineSLim; +		LL_DEBUGS("Voice") << "Buddy " << buddy->mDisplayName << " is SIP " << (result?"online":"offline") << LL_ENDL; +	} + +	if(!result) +	{ +		// This user isn't on the buddy list or doesn't show online status through the buddy list, but could be a participant in an existing session if they initiated a text IM. +		sessionState *session = findSession(id); +		if(session && !session->mHandle.empty()) +		{ +			if((session->mTextStreamState != streamStateUnknown) || (session->mMediaStreamState > streamStateIdle)) +			{ +				LL_DEBUGS("Voice") << "Open session with " << id << " found, returning SIP online state" << LL_ENDL; +				// we have a p2p text session open with this user, so by definition they're online. +				result = true; +			} +		} +	} +	 +	return result;  }  void LLVoiceClient::declineInvite(std::string &sessionHandle)  { -	sessionTerminateByHandle(sessionHandle); +	sessionState *session = findSession(sessionHandle); +	if(session) +	{ +		sessionMediaDisconnectSendMessage(session); +	}  }  void LLVoiceClient::leaveNonSpatialChannel()  { -	switchChannel(mSpatialSessionURI); +	LL_DEBUGS("Voice")  +		<< "called in state " << state2string(getState())  +		<< LL_ENDL; +	 +	// Make sure we don't rejoin the current session.	 +	sessionState *oldNextSession = mNextAudioSession; +	mNextAudioSession = NULL; +	 +	// Most likely this will still be the current session at this point, but check it anyway. +	reapSession(oldNextSession); +	 +	verifySessionState(); +	 +	sessionTerminate();  }  std::string LLVoiceClient::getCurrentChannel()  { +	std::string result; +	  	if((getState() == stateRunning) && !mSessionTerminateRequested)  	{ -		return mSessionURI; +		result = getAudioSessionURI();  	} -	return ""; +	return result;  }  bool LLVoiceClient::inProximalChannel() @@ -3401,7 +5321,7 @@ bool LLVoiceClient::inProximalChannel()  	if((getState() == stateRunning) && !mSessionTerminateRequested)  	{ -		result = !mNonSpatialChannel; +		result = inSpatialChannel();  	}  	return result; @@ -3413,7 +5333,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id)  	result = "sip:";  	result += nameFromID(id);  	result += "@"; -	result += mAccountServerName; +	result += mVoiceSIPURIHostName;  	return result;  } @@ -3426,7 +5346,7 @@ std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)  		result = "sip:";  		result += nameFromID(avatar->getID());  		result += "@"; -		result += mAccountServerName; +		result += mVoiceSIPURIHostName;  	}  	return result; @@ -3445,6 +5365,13 @@ std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar)  std::string LLVoiceClient::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 and diamondware code.  	result = "x"; @@ -3458,13 +5385,24 @@ std::string LLVoiceClient::nameFromID(const LLUUID &uuid)  	// If you need to transform a GUID to this form on the Mac OS X 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 LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid) +bool LLVoiceClient::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==" @@ -3486,6 +5424,13 @@ bool LLVoiceClient::IDFromName(const std::string name, LLUUID &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; @@ -3502,13 +5447,59 @@ std::string LLVoiceClient::sipURIFromName(std::string &name)  	result = "sip:";  	result += name;  	result += "@"; -	result += mAccountServerName; +	result += mVoiceSIPURIHostName;  //	LLStringUtil::toLower(result);  	return result;  } +std::string LLVoiceClient::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 LLVoiceClient::inSpatialChannel(void) +{ +	bool result = false; +	 +	if(mAudioSession) +		result = mAudioSession->mIsSpatial; +		 +	return result; +} + +std::string LLVoiceClient::getAudioSessionURI() +{ +	std::string result; +	 +	if(mAudioSession) +		result = mAudioSession->mSIPURI; +		 +	return result; +} + +std::string LLVoiceClient::getAudioSessionHandle() +{ +	std::string result; +	 +	if(mAudioSession) +		result = mAudioSession->mHandle; +		 +	return result; +} + +  /////////////////////////////  // Sending updates of current state @@ -3547,7 +5538,8 @@ void LLVoiceClient::updatePosition(void)  			LLMatrix3 rot;  			LLVector3d pos; -			// MBW -- XXX -- Setting both camera and avatar velocity to 0 for now.  May figure it out later... +			// 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());		 @@ -3562,7 +5554,7 @@ void LLVoiceClient::updatePosition(void)  			rot = agent->getRootJoint()->getWorldRotation().getMatrix3();  			pos = agent->getPositionGlobal(); -			// MBW -- XXX -- Can we get the head offset from outside the LLVOAvatar? +			// TODO: Can we get the head offset from outside the LLVOAvatar?  //			pos += LLVector3d(mHeadOffset);  			pos += LLVector3d(0.f, 0.f, 1.f); @@ -3668,8 +5660,8 @@ void LLVoiceClient::setVoiceEnabled(bool enabled)  		}  		else  		{ -			// for now, leave active channel, to auto join when turning voice back on -			//LLVoiceChannel::getCurrentVoiceChannel->deactivate(); +			// 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();  		}  	}  } @@ -3749,56 +5741,34 @@ void LLVoiceClient::setEarLocation(S32 loc)  void LLVoiceClient::setVoiceVolume(F32 volume)  { -	LL_DEBUGS("Voice") << "volume is " << volume << LL_ENDL; +	int scaled_volume = scale_speaker_volume(volume);	 -	// incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. -	// Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50 -	 -	volume -= 0.5f;		// offset volume to the range [-0.5 ... 0.5], with 0 at the default. -	int scaledVolume = 24;	// offset scaledVolume by its default level -	if(volume < 0.0f) -		scaledVolume += ((int)(volume * 248.0f));	// (24 - (-100)) * 2 -	else -		scaledVolume += ((int)(volume * 52.0f));	// (50 - 24) * 2 -	 -	if(scaledVolume != mSpeakerVolume) +	if(scaled_volume != mSpeakerVolume)  	{ -		if((scaledVolume == -100) || (mSpeakerVolume == -100)) +		if((scaled_volume == 0) || (mSpeakerVolume == 0))  		{  			mSpeakerMuteDirty = true;  		} -		mSpeakerVolume = scaledVolume; +		mSpeakerVolume = scaled_volume;  		mSpeakerVolumeDirty = true;  	}  }  void LLVoiceClient::setMicGain(F32 volume)  { -	int scaledVolume = ((int)(volume * 100.0f)) - 100; -	if(scaledVolume != mMicVolume) +	int scaled_volume = scale_mic_volume(volume); +	 +	if(scaled_volume != mMicVolume)  	{ -		mMicVolume = scaledVolume; +		mMicVolume = scaled_volume;  		mMicVolumeDirty = true;  	}  } -void LLVoiceClient::setVivoxDebugServerName(std::string &serverName) -{ -	if(!mAccountServerName.empty()) -	{ -		// The name has been filled in already, which means we know whether we're connecting to agni or not. -		if(!sConnectingToAgni) -		{ -			// Only use the setting if we're connecting to a development grid -- always use bhr when on agni. -			mAccountServerName = serverName; -		} -	} -} -  void LLVoiceClient::keyDown(KEY key, MASK mask)  {	 -	LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL; +//	LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL;  	if (gKeyboard->getKeyRepeated(key))  	{ @@ -3936,19 +5906,6 @@ BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)  	return result;  } -BOOL LLVoiceClient::getPTTPressed(const LLUUID& id) -{ -	BOOL result = FALSE; -	 -	participantState *participant = findParticipantByID(id); -	if(participant) -	{ -		result = participant->mPTT; -	} -	 -	return result; -} -  BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)  {  	BOOL result = FALSE; @@ -3980,144 +5937,849 @@ F32 LLVoiceClient::getUserVolume(const LLUUID& id)  void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)  { +	if(mAudioSession) +	{ +		participantState *participant = findParticipantByID(id); +		if (participant) +		{ +			// volume can amplify by as much as 4x! +			S32 ivol = (S32)(400.f * volume * volume); +			participant->mUserVolume = llclamp(ivol, 0, 400); +			participant->mVolumeDirty = TRUE; +			mAudioSession->mVolumeDirty = TRUE; +		} +	} +} + +std::string LLVoiceClient::getGroupID(const LLUUID& id) +{ +	std::string result; +  	participantState *participant = findParticipantByID(id); -	if (participant) +	if(participant)  	{ -		// volume can amplify by as much as 4x! -		S32 ivol = (S32)(400.f * volume * volume); -		participant->mUserVolume = llclamp(ivol, 0, 400); -		participant->mVolumeDirty = TRUE; -		mVolumeDirty = TRUE; +		result = participant->mGroupID;  	} +	 +	return result;  } +BOOL LLVoiceClient::getAreaVoiceDisabled() +{ +	return mAreaVoiceDisabled; +} + +void LLVoiceClient::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"; -LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id) +		writeString(stream.str()); +	} +} + +void LLVoiceClient::recordingLoopSave(const std::string& filename)  { -	serviceType result = serviceTypeUnknown; +//	LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; -	participantState *participant = findParticipantByID(id); -	if(participant) +	if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())  	{ -		result = participant->mServiceType; +		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 LLVoiceClient::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 LLVoiceClient::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 LLVoiceClient::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 LLVoiceClient::filePlaybackSetPaused(bool paused) +{ +	// TODO: Implement once Vivox gives me a sample +} + +void LLVoiceClient::filePlaybackSetMode(bool vox, float speed) +{ +	// TODO: Implement once Vivox gives me a sample +} + +LLVoiceClient::sessionState::sessionState() : +	mMediaStreamState(streamStateUnknown), +	mTextStreamState(streamStateUnknown), +	mCreateInProgress(false), +	mMediaConnectInProgress(false), +	mVoiceInvitePending(false), +	mTextInvitePending(false), +	mSynthesizedCallerID(false), +	mIsChannel(false), +	mIsSpatial(false), +	mIsP2P(false), +	mIncoming(false), +	mVoiceEnabled(false), +	mReconnect(false), +	mVolumeDirty(false), +	mParticipantsChanged(false) +{ +} + +LLVoiceClient::sessionState::~sessionState() +{ +	removeAllParticipants(); +} + +LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void) +{ +	return mSessions.begin(); +} + +LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void) +{ +	return mSessions.end(); +} + + +LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle) +{ +	sessionState *result = NULL; +	sessionMap::iterator iter = mSessionsByHandle.find(&handle); +	if(iter != mSessionsByHandle.end()) +	{ +		result = iter->second;  	}  	return result;  } -std::string LLVoiceClient::getGroupID(const LLUUID& id) +LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) +{	 +	sessionState *result = NULL; +	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) +	{ +		sessionState *session = *iter; +		if(session->mCreateInProgress && (session->mSIPURI == uri)) +		{ +			result = session; +			break; +		} +	} +	 +	return result; +} + +LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id)  { -	std::string result; +	sessionState *result = NULL; +	 +	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) +	{ +		sessionState *session = *iter; +		if(session->mCallerID == participant_id) +		{ +			result = session; +			break; +		} +	} +	 +	return result; +} -	participantState *participant = findParticipantByID(id); -	if(participant) +LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle) +{ +	sessionState *result = NULL; +	 +	if(handle.empty())  	{ -		result = participant->mGroupID; +		// No handle supplied. +		// Check whether there's already a session with this URI +		for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) +		{ +			sessionState *s = *iter; +			if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri)) +			{ +				// TODO: I need to think about this logic... it's possible that this case should raise an internal error. +				result = s; +				break; +			} +		} +	} +	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 = new sessionState(); +		result->mSIPURI = uri; +		result->mHandle = handle; +		 +		mSessions.insert(result); + +		if(!result->mHandle.empty()) +		{ +			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 LLVoiceClient::setSessionHandle(sessionState *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_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL; +			} + +			mSessionsByHandle.erase(iter); +		} +		else +		{ +			LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL; +		} +	} +			 +	session->mHandle = handle; + +	if(!handle.empty()) +	{ +		mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session)); +	} + +	verifySessionState(); +} + +void LLVoiceClient::setSessionURI(sessionState *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 LLVoiceClient::deleteSession(sessionState *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_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL +			} +			mSessionsByHandle.erase(iter); +		} +	} + +	// Remove the session from the URI map +	mSessions.erase(session); +	 +	// 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 = NULL; +		mAudioSessionChanged = true; +	} + +	// ditto for the next audio session +	if(mNextAudioSession == session) +	{ +		mNextAudioSession = NULL; +	} + +	// delete the session +	delete session; +} + +void LLVoiceClient::deleteAllSessions() +{ +	LL_DEBUGS("Voice") << "called" << LL_ENDL; + +	while(!mSessions.empty()) +	{ +		deleteSession(*(sessionsBegin())); +	} +	 +	if(!mSessionsByHandle.empty()) +	{ +		LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL +	} +} + +void LLVoiceClient::verifySessionState(void) +{ +	// This is mostly intended for debugging problems with session state management. +	LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL; + +	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) +	{ +		sessionState *session = *iter; + +		LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL; +		 +		if(!session->mHandle.empty()) +		{ +			// every session with a non-empty handle needs to be in the handle map +			sessionMap::iterator i2 = mSessionsByHandle.find(&(session->mHandle)); +			if(i2 == mSessionsByHandle.end()) +			{ +				LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL; +			} +			else +			{ +				if(i2->second != session) +				{ +					LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL; +				} +			} +		} +	} +		 +	// check that every entry in the handle map points to a valid session in the session set +	for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++) +	{ +		sessionState *session = iter->second; +		sessionIterator i2 = mSessions.find(session); +		if(i2 == mSessions.end()) +		{ +			LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL; +		} +		else +		{ +			if(session->mHandle != (*i2)->mHandle) +			{ +				LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL; +			} +		} +	} +} + +LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) : +	mURI(uri) +{ +	mOnlineSL = false; +	mOnlineSLim = false; +	mCanSeeMeOnline = true; +	mHasBlockListEntry = false; +	mHasAutoAcceptListEntry = false; +	mNameResolved = false; +	mInVivoxBuddies = false; +	mInSLFriends = false; +	mNeedsNameUpdate = false; +} + +void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName) +{ +	buddyListEntry *buddy = addBuddy(uri, displayName); +	buddy->mInVivoxBuddies = true;	 +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri) +{ +	std::string empty; +	buddyListEntry *buddy = addBuddy(uri, empty); +	if(buddy->mDisplayName.empty()) +	{ +		buddy->mNameResolved = false; +	} +	return buddy; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName) +{ +	buddyListEntry *result = NULL; +	buddyListMap::iterator iter = mBuddyListMap.find(&uri); +	 +	if(iter != mBuddyListMap.end()) +	{ +		// Found a matching buddy already in the map. +		LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL; +		result = iter->second; +	} + +	if(!result) +	{ +		// participant isn't already in one list or the other. +		LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL; +		result = new buddyListEntry(uri); +		result->mDisplayName = displayName; + +		if(IDFromName(uri, result->mUUID))  +		{ +			// Extracted UUID from name successfully. +		} +		else +		{ +			LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL; +		} + +		mBuddyListMap.insert(buddyListMap::value_type(&(result->mURI), result));  	}  	return result;  } -BOOL LLVoiceClient::getAreaVoiceDisabled() +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri)  { -	return mAreaVoiceDisabled; +	buddyListEntry *result = NULL; +	buddyListMap::iterator iter = mBuddyListMap.find(&uri); +	if(iter != mBuddyListMap.end()) +	{ +		result = iter->second; +	} +	 +	return result; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id) +{ +	buddyListEntry *result = NULL; +	buddyListMap::iterator iter; + +	for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) +	{ +		if(iter->second->mUUID == id) +		{ +			result = iter->second; +			break; +		} +	} +	 +	return result; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name) +{ +	buddyListEntry *result = NULL; +	buddyListMap::iterator iter; + +	for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) +	{ +		if(iter->second->mDisplayName == name) +		{ +			result = iter->second; +			break; +		} +	} +	 +	return result; +} + +void LLVoiceClient::deleteBuddy(const std::string &uri) +{ +	buddyListMap::iterator iter = mBuddyListMap.find(&uri); +	if(iter != mBuddyListMap.end()) +	{ +		LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL; +		buddyListEntry *buddy = iter->second; +		mBuddyListMap.erase(iter); +		delete buddy; +	} +	else +	{ +		LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL; +	} +	 +} + +void LLVoiceClient::deleteAllBuddies(void) +{ +	while(!mBuddyListMap.empty()) +	{ +		deleteBuddy(*(mBuddyListMap.begin()->first)); +	} +	 +	// Don't want to correlate with friends list when we've emptied the buddy list. +	mBuddyListMapPopulated = false; +	 +	// Don't want to correlate with friends list when we've reset the block rules. +	mBlockRulesListReceived = false; +	mAutoAcceptRulesListReceived = false; +} + +void LLVoiceClient::deleteAllBlockRules(void) +{ +	// Clear the block list entry flags from all local buddy list entries +	buddyListMap::iterator buddy_it; +	for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) +	{ +		buddy_it->second->mHasBlockListEntry = false; +	} +} + +void LLVoiceClient::deleteAllAutoAcceptRules(void) +{ +	// Clear the auto-accept list entry flags from all local buddy list entries +	buddyListMap::iterator buddy_it; +	for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) +	{ +		buddy_it->second->mHasAutoAcceptListEntry = false; +	} +} + +void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly) +{ +	buddyListEntry *buddy = NULL; + +	// blockMask is the SIP URI of a friends list entry +	buddyListMap::iterator iter = mBuddyListMap.find(&blockMask); +	if(iter != mBuddyListMap.end()) +	{ +		LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL; +		buddy = iter->second; +	} + +	if(buddy == NULL) +	{ +		LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL; +		buddy = addBuddy(blockMask); +	} +	 +	if(buddy != NULL) +	{ +		buddy->mHasBlockListEntry = true; +	} +} + +void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy) +{ +	buddyListEntry *buddy = NULL; + +	// blockMask is the SIP URI of a friends list entry +	buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask); +	if(iter != mBuddyListMap.end()) +	{ +		LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL; +		buddy = iter->second; +	} + +	if(buddy == NULL) +	{ +		LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL; +		buddy = addBuddy(autoAcceptMask); +	} + +	if(buddy != NULL) +	{ +		buddy->mHasAutoAcceptListEntry = true; +	} +} + +void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString) +{ +	// Block list entries were updated via addBlockRule() during parsing.  Just flag that we're done. +	mBlockRulesListReceived = true; +} + +void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString) +{ +	// Block list entries were updated via addBlockRule() during parsing.  Just flag that we're done. +	mAutoAcceptRulesListReceived = true;  }  void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)  { -	mObservers.insert(observer); +	mParticipantObservers.insert(observer);  }  void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)  { -	mObservers.erase(observer); +	mParticipantObservers.erase(observer);  } -void LLVoiceClient::notifyObservers() +void LLVoiceClient::notifyParticipantObservers()  { -	for (observer_set_t::iterator it = mObservers.begin(); -		it != mObservers.end(); +	for (observer_set_t::iterator it = mParticipantObservers.begin(); +		it != mParticipantObservers.end();  		)  	{  		LLVoiceClientParticipantObserver* observer = *it;  		observer->onChange();  		// In case onChange() deleted an entry. -		it = mObservers.upper_bound(observer); +		it = mParticipantObservers.upper_bound(observer);  	}  } -void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer) +void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)  {  	mStatusObservers.insert(observer);  } -void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer) +void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)  {  	mStatusObservers.erase(observer);  }  void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)  { -	if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) +	if(mAudioSession)  	{ -		switch(mVivoxErrorStatusCode) +		if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)  		{ -		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. -		mVivoxErrorStatusCode = 0; -	} -	 -	if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL  -		//NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT -		&& (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408))  -	{ -		// call failed because other user was not available -		// treat this as an error case -		status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; +			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. -		mVivoxErrorStatusCode = 0; +			// 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 404:	// NOT_FOUND +				case 480:	// TEMPORARILY_UNAVAILABLE +				case 408:	// 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 " << mSessionURI << LL_ENDL; +		 +	LL_DEBUGS("Voice")  +		<< " " << LLVoiceClientStatusObserver::status2string(status)   +		<< ", session URI " << getAudioSessionURI()  +		<< (inSpatialChannel()?", proximal is true":", proximal is false") +	<< LL_ENDL;  	for (status_observer_set_t::iterator it = mStatusObservers.begin();  		it != mStatusObservers.end();  		)  	{  		LLVoiceClientStatusObserver* observer = *it; -		observer->onChange(status, mSessionURI, !mNonSpatialChannel); +		observer->onChange(status, getAudioSessionURI(), inSpatialChannel());  		// In case onError() deleted an entry.  		it = mStatusObservers.upper_bound(observer);  	}  } +void LLVoiceClient::addObserver(LLFriendObserver* observer) +{ +	mFriendObservers.insert(observer); +} + +void LLVoiceClient::removeObserver(LLFriendObserver* observer) +{ +	mFriendObservers.erase(observer); +} + +void LLVoiceClient::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 LLVoiceClient::lookupName(const LLUUID &id) +{ +	gCacheName->getName(id, onAvatarNameLookup); +} +  //static -// void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) -// { -// 	participantState* statep = gVoiceClient->findParticipantByID(id); +void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) +{ +	if(gVoiceClient) +	{ +		std::string name = llformat("%s %s", first.c_str(), last.c_str()); +		gVoiceClient->avatarNameResolved(id, name); +	} +} -// 	if (statep) -// 	{ -// 		statep->mDisplayName = first + " " + last; -// 	} +void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ +	// If the avatar whose name just resolved is on our friends list, resync the friends list. +	if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL) +	{ +		mFriendsListDirty = true; +	} -// 	gVoiceClient->notifyObservers(); -// } +	// Iterate over all sessions. +	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) +	{ +		sessionState *session = *iter; + +		// Check for this user as a participant in this session +		participantState *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 gIMMgr->addP2PSession() here.  The first incoming message will create the panel.				 +			} +			if(session->mVoiceInvitePending) +			{ +				session->mVoiceInvitePending = false; + +				gIMMgr->inviteToSession( +					session->mIMSessionID, +					session->mName, +					session->mCallerID,  +					session->mName,  +					IM_SESSION_P2P_INVITE,  +					LLIMMgr::INVITATION_TYPE_VOICE, +					session->mHandle, +					session->mSIPURI); +			} +			 +		} +	} +}  class LLViewerParcelVoiceInfo : public LLHTTPNode  { diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 4ffe5edf71..43bbc8e29c 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -42,6 +42,7 @@ class LLVivoxProtocolParser;  #include "v3math.h"  #include "llframetimer.h"  #include "llviewerregion.h" +#include "llcallingcard.h"   // for LLFriendObserver  class LLVoiceClientParticipantObserver  { @@ -92,41 +93,10 @@ class LLVoiceClient: public LLSingleton<LLVoiceClient>  	public: -		enum serviceType -		{ -			serviceTypeUnknown,	// Unknown, returned if no data on the avatar is available -			serviceTypeA,		// spatialized local chat -			serviceTypeB,		// remote multi-party chat -			serviceTypeC		// one-to-one and small group chat -		};  		static F32 OVERDRIVEN_POWER_LEVEL;  		void updateSettings(); // call after loading settings and whenever they change -		///////////////////////////// -		// session control messages -		void connect(); - -		void connectorCreate(); -		void connectorShutdown(); - -		void requestVoiceAccountProvision(S32 retries = 3); -		void userAuthorized( -			const std::string& firstName, -			const std::string& lastName, -			const LLUUID &agentID); -		void login(const std::string& accountName, const std::string &password); -		void loginSendMessage(); -		void logout(); -		void logoutSendMessage(); -		 -		void channelGetListSendMessage(); -		void sessionCreateSendMessage(); -		void sessionConnectSendMessage(); -		void sessionTerminate(); -		void sessionTerminateSendMessage(); -		void sessionTerminateByHandle(std::string &sessionHandle); -		  		void getCaptureDevicesSendMessage();  		void getRenderDevicesSendMessage(); @@ -171,23 +141,32 @@ class LLVoiceClient: public LLSingleton<LLVoiceClient>  		/////////////////////////////  		// Response/Event handlers -		void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle); -		void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle); -		void channelGetListResponse(int statusCode, std::string &statusString); -		void sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle); -		void sessionConnectResponse(int statusCode, std::string &statusString); -		void sessionTerminateResponse(int statusCode, std::string &statusString); +		void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); +		void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); +		void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); +		void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); +		void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString);  		void logoutResponse(int statusCode, std::string &statusString);  		void connectorShutdownResponse(int statusCode, std::string &statusString); -		void loginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); -		void sessionNewEvent(std::string &accountHandle, std::string &eventSessionHandle, int state, std::string &nameString, std::string &uriString); -		void sessionStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, std::string &sessionHandle, int state,  bool isChannel, std::string &nameString); -		void participantStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, int state,  std::string &nameString, std::string &displayNameString, int participantType); -		void participantPropertiesEvent(std::string &uriString, int statusCode, std::string &statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); +		void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); +		void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); +		void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming); +		void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); +		void sessionGroupAddedEvent(std::string &sessionGroupHandle); +		void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); +		void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); +		void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); +		void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy);  		void auxAudioPropertiesEvent(F32 energy); -	 +		void buddyPresenceEvent(std::string &uriString, std::string &alias, std::string &statusString, std::string &applicationString); +		void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); +		void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); +		void subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType); +		 +		void buddyListChanged();  		void muteListChanged(); +		void updateFriends(U32 mask);  		/////////////////////////////  		// Sending updates of current state @@ -211,7 +190,6 @@ static	void updatePosition(void);  		void setVoiceVolume(F32 volume);  		void setMicGain(F32 volume);  		void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) -		void setVivoxDebugServerName(std::string &serverName);  		void setLipSyncEnabled(BOOL enabled);  		BOOL lipSyncEnabled(); @@ -226,57 +204,261 @@ static	void updatePosition(void);  		BOOL getIsSpeaking(const LLUUID& id);  		BOOL getIsModeratorMuted(const LLUUID& id);  		F32 getCurrentPower(const LLUUID& id);		// "power" is related to "amplitude" in a defined way.  I'm just not sure what the formula is... -		BOOL getPTTPressed(const LLUUID& id);			// This is the inverse of the "locally muted" property.  		BOOL getOnMuteList(const LLUUID& id);  		F32 getUserVolume(const LLUUID& id);  		std::string getDisplayName(const LLUUID& id);  		// MBW -- XXX -- Not sure how to get this data out of the TVC  		BOOL getUsingPTT(const LLUUID& id); -		serviceType getServiceType(const LLUUID& id);	// type of chat the user is involved in (see bHear scope doc for definitions of A/B/C)  		std::string getGroupID(const LLUUID& id);		// group ID if the user is in group chat (empty string if not applicable)  		/////////////////////////////  		BOOL getAreaVoiceDisabled();		// returns true if the area the avatar is in is speech-disabled.  											// Use this to determine whether to show a "no speech" icon in the menu bar. +		 +		///////////////////////////// +		// Recording controls +		void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); +		void recordingLoopSave(const std::string& filename); +		void recordingStop(); +		 +		// Playback controls +		void filePlaybackStart(const std::string& filename); +		void filePlaybackStop(); +		void filePlaybackSetPaused(bool paused); +		void filePlaybackSetMode(bool vox = false, float speed = 1.0f); +		 +		 +		// This is used by the string-keyed maps below, to avoid storing the string twice. +		// The 'const std::string *' in the key points to a string actually stored in the object referenced by the map. +		// The add and delete operations for each map allocate and delete in the right order to avoid dangling references. +		// The default compare operation would just compare pointers, which is incorrect, so they must use this comparitor instead. +		struct stringMapComparitor +		{ +			bool operator()(const std::string* a, const std::string * b) const +			{ +				return a->compare(*b) < 0; +			} +		}; +		struct uuidMapComparitor +		{ +			bool operator()(const LLUUID* a, const LLUUID * b) const +			{ +				return *a < *b; +			} +		}; +		  		struct participantState  		{  		public:  			participantState(const std::string &uri); + +			bool updateMuteState(); +  			std::string mURI; -			std::string mName; +			LLUUID mAvatarID; +			std::string mAccountName;  			std::string mDisplayName; -			bool mPTT; -			bool mIsSpeaking; -			bool mIsModeratorMuted;  			LLFrameTimer mSpeakingTimeout;  			F32	mLastSpokeTimestamp;  			F32 mPower;  			int mVolume; -			serviceType mServiceType;  			std::string mGroupID; -			bool mOnMuteList;		// true if this avatar is on the user's mute list (and should be muted)  			int mUserVolume; +			bool mPTT; +			bool mIsSpeaking; +			bool mIsModeratorMuted; +			bool mOnMuteList;		// true if this avatar is on the user's mute list (and should be muted)  			bool mVolumeDirty;		// true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed)  			bool mAvatarIDValid; -			LLUUID mAvatarID; +			bool mIsSelf; +		}; +		typedef std::map<const std::string *, participantState*, stringMapComparitor> participantMap; + +		typedef std::map<const LLUUID *, participantState*, uuidMapComparitor> participantUUIDMap; +	 +		enum streamState +		{ +			streamStateUnknown = 0, +			streamStateIdle = 1, +			streamStateConnected = 2, +			streamStateRinging = 3,  		}; -		typedef std::map<std::string, participantState*> participantMap; -		participantState *findParticipant(const std::string &uri); -		participantState *findParticipantByAvatar(LLVOAvatar *avatar); +		struct sessionState +		{ +		public: +			sessionState(); +			~sessionState(); + +			participantState *addParticipant(const std::string &uri); +			// Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. +			// Take care not to use the pointer again after that. +			void removeParticipant(participantState *participant); +			void removeAllParticipants(); + +			participantState *findParticipant(const std::string &uri); +			participantState *findParticipantByID(const LLUUID& id); + +			std::string mHandle; +			std::string mGroupHandle; +			std::string mSIPURI; +			std::string mAlias; +			std::string mName; +			std::string mAlternateSIPURI; +			std::string mHash;			// Channel password +			std::string mErrorStatusString; +			std::queue<std::string> mTextMsgQueue; +			 +			LLUUID		mIMSessionID; +			LLUUID		mCallerID; +			int			mErrorStatusCode; +			int			mMediaStreamState; +			int			mTextStreamState; +			bool		mCreateInProgress;	// True if a Session.Create has been sent for this session and no response has been received yet. +			bool		mMediaConnectInProgress;	// True if a Session.MediaConnect has been sent for this session and no response has been received yet. +			bool		mVoiceInvitePending;	// True if a voice invite is pending for this session (usually waiting on a name lookup) +			bool		mTextInvitePending;		// True if a text invite is pending for this session (usually waiting on a name lookup) +			bool		mSynthesizedCallerID;	// True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. +			bool		mIsChannel;	// True for both group and spatial channels (false for p2p, PSTN) +			bool		mIsSpatial;	// True for spatial channels +			bool		mIsP2P; +			bool		mIncoming; +			bool		mVoiceEnabled; +			bool		mReconnect;	// Whether we should try to reconnect to this session if it's dropped +			// Set to true when the mute state of someone in the participant list changes. +			// The code will have to walk the list to find the changed participant(s). +			bool		mVolumeDirty; + +			bool		mParticipantsChanged; +			participantMap mParticipantsByURI; +			participantUUIDMap mParticipantsByUUID; +		}; +  		participantState *findParticipantByID(const LLUUID& id); -		  		participantMap *getParticipantList(void); +		 +		typedef std::map<const std::string*, sessionState*, stringMapComparitor> sessionMap; +		typedef std::set<sessionState*> sessionSet; +				 +		typedef sessionSet::iterator sessionIterator; +		sessionIterator sessionsBegin(void); +		sessionIterator sessionsEnd(void); + +		sessionState *findSession(const std::string &handle); +		sessionState *findSessionBeingCreatedByURI(const std::string &uri); +		sessionState *findSession(const LLUUID &participant_id); +		sessionState *findSessionByCreateID(const std::string &create_id); +		 +		sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null); +		void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null); +		void setSessionURI(sessionState *session, const std::string &uri); +		void deleteSession(sessionState *session); +		void deleteAllSessions(void); + +		void verifySessionState(void); + +		void joinedAudioSession(sessionState *session); +		void leftAudioSession(sessionState *session); + +		// This is called in several places where the session _may_ need to be deleted. +		// It contains logic for whether to delete the session or keep it around. +		void reapSession(sessionState *session); +		 +		// Returns true if the session seems to indicate we've moved to a region on a different voice server +		bool sessionNeedsRelog(sessionState *session); +		 +		struct buddyListEntry +		{ +			buddyListEntry(const std::string &uri); +			std::string mURI; +			std::string mDisplayName; +			LLUUID	mUUID; +			bool mOnlineSL; +			bool mOnlineSLim; +			bool mCanSeeMeOnline; +			bool mHasBlockListEntry; +			bool mHasAutoAcceptListEntry; +			bool mNameResolved; +			bool mInSLFriends; +			bool mInVivoxBuddies; +			bool mNeedsNameUpdate; +		}; + +		typedef std::map<const std::string*, buddyListEntry*, stringMapComparitor> buddyListMap; +		 +		// This should be called when parsing a buddy list entry sent by SLVoice.		 +		void processBuddyListEntry(const std::string &uri, const std::string &displayName); + +		buddyListEntry *addBuddy(const std::string &uri); +		buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName); +		buddyListEntry *findBuddy(const std::string &uri); +		buddyListEntry *findBuddy(const LLUUID &id); +		buddyListEntry *findBuddyByDisplayName(const std::string &name); +		void deleteBuddy(const std::string &uri); +		void deleteAllBuddies(void); + +		void deleteAllBlockRules(void); +		void addBlockRule(const std::string &blockMask, const std::string &presenceOnly); +		void deleteAllAutoAcceptRules(void); +		void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy); +		void accountListBlockRulesResponse(int statusCode, const std::string &statusString);						 +		void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);						 +		 +		///////////////////////////// +		// session control messages +		void connectorCreate(); +		void connectorShutdown(); +		void requestVoiceAccountProvision(S32 retries = 3); +		void userAuthorized( +			const std::string& firstName, +			const std::string& lastName, +			const LLUUID &agentID); +		void login( +			const std::string& account_name, +			const std::string& password, +			const std::string& voice_sip_uri_hostname, +			const std::string& voice_account_server_uri); +		void loginSendMessage(); +		void logout(); +		void logoutSendMessage(); + +		void accountListBlockRulesSendMessage(); +		void accountListAutoAcceptRulesSendMessage(); +		 +		void sessionGroupCreateSendMessage(); +		void sessionCreateSendMessage(sessionState *session, bool startAudio = true, bool startText = false); +		void sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio = true, bool startText = false); +		void sessionMediaConnectSendMessage(sessionState *session);		// just joins the audio session +		void sessionTextConnectSendMessage(sessionState *session);		// just joins the text session +		void sessionTerminateSendMessage(sessionState *session); +		void sessionMediaDisconnectSendMessage(sessionState *session); +		void sessionTextDisconnectSendMessage(sessionState *session); + +		// Pokes the state machine to leave the audio session next time around. +		void sessionTerminate();	 +		 +		// Pokes the state machine to shut down the connector and restart it. +		void requestRelog(); +		 +		// Does the actual work to get out of the audio session +		void leaveAudioSession(); +		  		void addObserver(LLVoiceClientParticipantObserver* observer);  		void removeObserver(LLVoiceClientParticipantObserver* observer); -		void addStatusObserver(LLVoiceClientStatusObserver* observer); -		void removeStatusObserver(LLVoiceClientStatusObserver* observer); +		void addObserver(LLVoiceClientStatusObserver* observer); +		void removeObserver(LLVoiceClientStatusObserver* observer); + +		void addObserver(LLFriendObserver* observer); +		void removeObserver(LLFriendObserver* observer); +		 +		void lookupName(const LLUUID &id); +		static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data); +		void avatarNameResolved(const LLUUID &id, const std::string &name); -// 		static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data);  		typedef std::vector<std::string> deviceList;  		deviceList *getCaptureDevices(); @@ -288,8 +470,16 @@ static	void updatePosition(void);  		void setSpatialChannel(  			const std::string &uri,  			const std::string &credentials); -		void callUser(LLUUID &uuid); -		void answerInvite(std::string &sessionHandle, LLUUID& other_user_id); +		// start a voice session with the specified user +		void callUser(const LLUUID &uuid); +		 +		// Send a text message to the specified user, initiating the session if necessary. +		bool sendTextMessage(const LLUUID& participant_id, const std::string& message); +		 +		// close any existing text IM session with the specified user +		void endUserIMSession(const LLUUID &uuid); +		 +		bool answerInvite(std::string &sessionHandle);  		void declineInvite(std::string &sessionHandle);  		void leaveNonSpatialChannel(); @@ -302,33 +492,37 @@ static	void updatePosition(void);  		bool inProximalChannel();  		std::string sipURIFromID(const LLUUID &id); - +				 +		// Returns true if the indicated user is online via SIP presence according to SLVoice. +		// Note that we only get SIP presence data for other users that are in our vivox buddy list. +		bool isOnlineSIP(const LLUUID &id); +		  	private:  		// internal state for a simple state machine.  This is used to deal with the asynchronous nature of some of the messages.  		// Note: if you change this list, please make corresponding changes to LLVoiceClient::state2string().  		enum state  		{ +			stateDisableCleanup,  			stateDisabled,				// Voice is turned off.  			stateStart,					// Class is initialized, socket is created  			stateDaemonLaunched,		// Daemon has been launched  			stateConnecting,			// connect() call has been issued +			stateConnected,				// connection to the daemon has been made, send some initial setup commands.  			stateIdle,					// socket is connected, ready for messaging +			stateMicTuningStart, +			stateMicTuningRunning,		 +			stateMicTuningStop,  			stateConnectorStart,		// connector needs to be started  			stateConnectorStarting,		// waiting for connector handle  			stateConnectorStarted,		// connector handle received -			stateMicTuningNoLogin,		// mic tuning before login  			stateLoginRetry,			// need to retry login (failed due to changing password)  			stateLoginRetryWait,		// waiting for retry timer  			stateNeedsLogin,			// send login request  			stateLoggingIn,				// waiting for account handle  			stateLoggedIn,				// account handle received +			stateCreatingSessionGroup,	// Creating the main session group  			stateNoChannel,				//  -			stateMicTuningStart, -			stateMicTuningRunning,		 -			stateMicTuningStop, -			stateSessionCreate,			// need to send Session.Create command -			stateSessionConnect,		// need to send Session.Connect command  			stateJoiningSession,		// waiting for session handle  			stateSessionJoined,			// session handle received  			stateRunning,				// in session, steady state @@ -355,7 +549,7 @@ static	void updatePosition(void);  		state mState;  		bool mSessionTerminateRequested; -		bool mNonSpatialChannel; +		bool mRelogRequested;  		void setState(state inState);  		state getState(void)  { return mState; }; @@ -378,18 +572,7 @@ static	void updatePosition(void);  		std::string mAccountDisplayName;  		std::string mAccountFirstName;  		std::string mAccountLastName; -		 -		std::string mNextP2PSessionURI;		// URI of the P2P session to join next -		std::string mNextSessionURI;		// URI of the session to join next -		std::string mNextSessionHandle;		// Session handle of the session to join next -		std::string mNextSessionHash;		// Password hash for the session to join next -		bool mNextSessionSpatial;			// Will next session be a spatial chat? -		bool mNextSessionNoReconnect;		// Next session should not auto-reconnect (i.e. user -> user chat) -		bool mNextSessionResetOnClose;		// If this is true, go back to spatial chat when the next session terminates. -		 -		std::string mSessionStateEventHandle;	// session handle received in SessionStateChangeEvents -		std::string mSessionStateEventURI;		// session URI received in SessionStateChangeEvents -		 +				  		bool mTuningMode;  		float mTuningEnergy;  		std::string mTuningAudioFile; @@ -400,32 +583,40 @@ static	void updatePosition(void);  		state mTuningExitState;					// state to return to when we leave tuning mode.  		std::string mSpatialSessionURI; -		 -		bool mSessionResetOnClose; -		 -		int mVivoxErrorStatusCode;		 -		std::string mVivoxErrorStatusString; +		std::string mSpatialSessionCredentials; + +		std::string mMainSessionGroupHandle; // handle of the "main" session group.  		std::string mChannelName;			// Name of the channel to be looked up   		bool mAreaVoiceDisabled; -		std::string mSessionURI;			// URI of the session we're in. -		bool mSessionP2P;					// true if this session is a p2p call +		sessionState *mAudioSession;		// Session state for the current audio session +		bool mAudioSessionChanged;			// set to true when the above pointer gets changed, so observers can be notified. + +		sessionState *mNextAudioSession;	// Session state for the audio session we're trying to join + +//		std::string mSessionURI;			// URI of the session we're in. +//		std::string mSessionHandle;		// returned by ?  		S32 mCurrentParcelLocalID;			// Used to detect parcel boundary crossings  		std::string mCurrentRegionName;		// Used to detect parcel boundary crossings  		std::string mConnectorHandle;	// returned by "Create Connector" message  		std::string mAccountHandle;		// returned by login message		 -		std::string mSessionHandle;		// returned by ? +		int 		mNumberOfAliases;  		U32 mCommandCookie; -		std::string mAccountServerName; -		std::string mAccountServerURI; +		std::string mVoiceAccountServerURI; +		std::string mVoiceSIPURIHostName;  		int mLoginRetryCount; -		participantMap mParticipantMap; -		bool mParticipantMapChanged; +		sessionMap mSessionsByHandle;				// Active sessions, indexed by session handle.  Sessions which are being initiated may not be in this map. +		sessionSet mSessions;						// All sessions, not indexed.  This is the canonical session list. +		 +		bool mBuddyListMapPopulated; +		bool mBlockRulesListReceived; +		bool mAutoAcceptRulesListReceived; +		buddyListMap mBuddyListMap;  		deviceList mCaptureDevices;  		deviceList mRenderDevices; @@ -435,40 +626,41 @@ static	void updatePosition(void);  		bool mCaptureDeviceDirty;  		bool mRenderDeviceDirty; -		participantState *addParticipant(const std::string &uri); -		// Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. -		// Take care not to use the pointer again after that. -		void removeParticipant(participantState *participant); -		void removeAllParticipants(); - -		void updateMuteState(participantState *participant); - -		typedef std::map<std::string, std::string> channelMap; -		channelMap mChannelMap; -		 -		// These are used by the parser when processing a channel list response. -		void clearChannelMap(void); -		void addChannelMapEntry(std::string &name, std::string &uri); -		std::string findChannelURI(std::string &name); -			  		// This should be called when the code detects we have changed parcels.  		// It initiates the call to the server that gets the parcel channel.  		void parcelChanged(); -	void switchChannel(std::string uri = std::string(), bool spatial = true, bool noReconnect = false, std::string hash = ""); -		void joinSession(std::string handle, std::string uri); +	void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); +		void joinSession(sessionState *session); -		std::string nameFromAvatar(LLVOAvatar *avatar); -		std::string nameFromID(const LLUUID &id); -		bool IDFromName(const std::string name, LLUUID &uuid); -		std::string displayNameFromAvatar(LLVOAvatar *avatar); +static 	std::string nameFromAvatar(LLVOAvatar *avatar); +static	std::string nameFromID(const LLUUID &id); +static	bool IDFromName(const std::string name, LLUUID &uuid); +static	std::string displayNameFromAvatar(LLVOAvatar *avatar);  		std::string sipURIFromAvatar(LLVOAvatar *avatar);  		std::string sipURIFromName(std::string &name); +		 +		// Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. +static	std::string nameFromsipURI(const std::string &uri);		 + +		bool inSpatialChannel(void); +		std::string getAudioSessionURI(); +		std::string getAudioSessionHandle();  		void sendPositionalUpdate(void);  		void buildSetCaptureDevice(std::ostringstream &stream);  		void buildSetRenderDevice(std::ostringstream &stream); +		void buildLocalAudioUpdates(std::ostringstream &stream); +		 +		void clearAllLists(); +		void checkFriend(const LLUUID& id); +		void sendFriendsListUpdates(); + +		// start a text IM session with the specified user +		// This will be asynchronous, the session may be established at a future time. +		sessionState* startUserIMSession(const LLUUID& uuid); +		void sendQueuedTextMessages(sessionState *session);  		void enforceTether(void); @@ -492,10 +684,9 @@ static	void updatePosition(void);  		bool		mPTTIsToggle;  		bool		mUserPTTState;  		bool		mMuteMic; -		 -		// Set to true when the mute state of someone in the participant list changes. -		// The code will have to walk the list to find the changed participant(s). -		bool		mVolumeDirty; +				 +		// Set to true when the friends list is known to have changed. +		bool		mFriendsListDirty;  		enum  		{ @@ -523,14 +714,18 @@ static	void updatePosition(void);  		BOOL		mLipSyncEnabled;  		typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; -		observer_set_t mObservers; +		observer_set_t mParticipantObservers; -		void notifyObservers(); +		void notifyParticipantObservers();  		typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t;  		status_observer_set_t mStatusObservers;  		void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); + +		typedef std::set<LLFriendObserver*> friend_observer_set_t; +		friend_observer_set_t mFriendObservers; +		void notifyFriendObservers();  };  extern LLVoiceClient *gVoiceClient; diff --git a/indra/newview/skins/default/textures/slim_icon_16_viewer.tga b/indra/newview/skins/default/textures/slim_icon_16_viewer.tgaBinary files differ new file mode 100644 index 0000000000..552181d36a --- /dev/null +++ b/indra/newview/skins/default/textures/slim_icon_16_viewer.tga diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 249787ad8d..c79c2aedf1 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -237,11 +237,6 @@ class WindowsManifest(ViewerManifest):          # Vivox runtimes          if self.prefix(src="vivox-runtime/i686-win32", dst=""):              self.path("SLVoice.exe") -            self.path("SLVoiceAgent.exe") -            self.path("libeay32.dll") -            self.path("srtp.dll") -            self.path("ssleay32.dll") -            self.path("tntk.dll")              self.path("alut.dll")              self.path("vivoxsdk.dll")              self.path("ortp.dll") @@ -451,7 +446,6 @@ class DarwinManifest(ViewerManifest):                  self.path("vivox-runtime/universal-darwin/libortp.dylib", "libortp.dylib")                  self.path("vivox-runtime/universal-darwin/libvivoxsdk.dylib", "libvivoxsdk.dylib")                  self.path("vivox-runtime/universal-darwin/SLVoice", "SLVoice") -                self.path("vivox-runtime/universal-darwin/SLVoiceAgent.app", "SLVoiceAgent.app")                  # llkdu dynamic library                  self.path("../../libraries/universal-darwin/lib_release/libllkdu.dylib", "libllkdu.dylib") | 
