summaryrefslogtreecommitdiff
path: root/indra/newview/llvoiceclient.cpp
diff options
context:
space:
mode:
authorMonroe Williams <monroe@lindenlab.com>2009-02-27 21:01:19 +0000
committerMonroe Williams <monroe@lindenlab.com>2009-02-27 21:01:19 +0000
commitdd437009e88954fd0fe9dd95b903dbd1ea52e901 (patch)
tree5f5cb97ebc5dc4ca3bdbe2867a897e625c54a011 /indra/newview/llvoiceclient.cpp
parent0bd557510a20565a4f27318f86dd11dac88ff574 (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/llvoiceclient.cpp')
-rw-r--r--indra/newview/llvoiceclient.cpp4654
1 files changed, 3658 insertions, 996 deletions
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 &lt;, &gt;, and &amp;
+ mark = 0;
+ while((mark = rawMessage.find("&lt;", mark)) != std::string::npos)
+ {
+ rawMessage.replace(mark, 4, "<");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = rawMessage.find("&gt;", mark)) != std::string::npos)
+ {
+ rawMessage.replace(mark, 4, ">");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = rawMessage.find("&amp;", 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 &notificationType)
+{
+ 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
{