diff options
author | Monroe Williams <monroe@lindenlab.com> | 2009-02-27 21:01:19 +0000 |
---|---|---|
committer | Monroe Williams <monroe@lindenlab.com> | 2009-02-27 21:01:19 +0000 |
commit | dd437009e88954fd0fe9dd95b903dbd1ea52e901 (patch) | |
tree | 5f5cb97ebc5dc4ca3bdbe2867a897e625c54a011 /indra/newview/llvoiceclient.cpp | |
parent | 0bd557510a20565a4f27318f86dd11dac88ff574 (diff) |
svn merge -r 113014:113017 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-QAR-1323
Merging in QAR-1323.
Diffstat (limited to 'indra/newview/llvoiceclient.cpp')
-rw-r--r-- | indra/newview/llvoiceclient.cpp | 4654 |
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 <, >, and & + mark = 0; + while((mark = rawMessage.find("<", mark)) != std::string::npos) + { + rawMessage.replace(mark, 4, "<"); + mark += 1; + } + + mark = 0; + while((mark = rawMessage.find(">", mark)) != std::string::npos) + { + rawMessage.replace(mark, 4, ">"); + mark += 1; + } + + mark = 0; + while((mark = rawMessage.find("&", mark)) != std::string::npos) + { + rawMessage.replace(mark, 5, "&"); + mark += 1; + } + } + + // strip leading/trailing whitespace (since we always seem to get a couple newlines) + LLStringUtil::trim(rawMessage); + +// LL_DEBUGS("Voice") << " stripped message = \n" << rawMessage << LL_ENDL; + + sessionState *session = findSession(sessionHandle); + if(session) + { + bool is_busy = gAgent.getBusy(); + bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); + bool is_linden = LLMuteList::getInstance()->isLinden(session->mName); + bool quiet_chat = false; + LLChat chat; + + chat.mMuted = is_muted && !is_linden; + + if(!chat.mMuted) + { + chat.mFromID = session->mCallerID; + chat.mFromName = session->mName; + chat.mSourceType = CHAT_SOURCE_AGENT; + + if(is_busy && !is_linden) + { + quiet_chat = true; + // TODO: Question: Return busy mode response here? Or maybe when session is started instead? + } + + std::string fullMessage = std::string(": ") + rawMessage; + + LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; + gIMMgr->addMessage(session->mIMSessionID, + session->mCallerID, + session->mName.c_str(), + fullMessage.c_str(), + LLStringUtil::null, // default arg + IM_NOTHING_SPECIAL, // default arg + 0, // default arg + LLUUID::null, // default arg + LLVector3::zero, // default arg + true); // prepend name and make it a link to the user's profile + + chat.mText = std::string("IM: ") + session->mName + std::string(": ") + rawMessage; + // If the chat should come in quietly (i.e. we're in busy mode), pretend it's from a local agent. + LLFloaterChat::addChat( chat, TRUE, quiet_chat ); + } + } + } +} + +void LLVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) +{ + sessionState *session = findSession(sessionHandle); + + if(session) + { + participantState *participant = session->findParticipant(uriString); + if(participant) + { + if (!stricmp(notificationType.c_str(), "Typing")) + { + // Other end started typing + // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). + // It requires an LLIMInfo for the message, which we don't have here. + } + else if (!stricmp(notificationType.c_str(), "NotTyping")) + { + // Other end stopped typing + // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). + // It requires an LLIMInfo for the message, which we don't have here. + } + else + { + LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; + } +} + +void LLVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType) +{ + buddyListEntry *buddy = findBuddy(buddyURI); + + if(!buddy) + { + // Couldn't find buddy by URI, try converting the alias... + if(!alias.empty()) + { + LLUUID id; + if(IDFromName(alias, id)) + { + buddy = findBuddy(id); + } + } + } + + if(buddy) + { + std::ostringstream stream; + + if(buddy->mCanSeeMeOnline) + { + // Sending the response will create an auto-accept rule + buddy->mHasAutoAcceptListEntry = true; + } + else + { + // Sending the response will create a block rule + buddy->mHasBlockListEntry = true; + } + + if(buddy->mInSLFriends) + { + buddy->mInVivoxBuddies = true; + } + + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.SendSubscriptionReply.1\">" + << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" + << "<BuddyURI>" << buddy->mURI << "</BuddyURI>" + << "<RuleType>" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "</RuleType>" + << "<AutoAccept>"<< (buddy->mInSLFriends?"1":"0")<< "</AutoAccept>" + << "<SubscriptionHandle>" << subscriptionHandle << "</SubscriptionHandle>" + << "</Request>" + << "\n\n\n"; + + writeString(stream.str()); } } @@ -3037,155 +4713,226 @@ void LLVoiceClient::auxAudioPropertiesEvent(F32 energy) mTuningEnergy = energy; } +void LLVoiceClient::buddyListChanged() +{ + // This is called after we receive a BuddyAndGroupListChangedEvent. + mBuddyListMapPopulated = true; + mFriendsListDirty = true; +} + void LLVoiceClient::muteListChanged() { // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. - - participantMap::iterator iter = mParticipantMap.begin(); - - for(; iter != mParticipantMap.end(); iter++) + if(mAudioSession) { - participantState *p = iter->second; + participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - // Check to see if this participant is on the mute list already - updateMuteState(p); + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) + { + participantState *p = iter->second; + + // Check to see if this participant is on the mute list already + if(p->updateMuteState()) + mAudioSession->mVolumeDirty = true; + } + } +} + +void LLVoiceClient::updateFriends(U32 mask) +{ + if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::POWERS)) + { + // Just resend the whole friend list to the daemon + mFriendsListDirty = true; } } ///////////////////////////// // Managing list of participants LLVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), mPTT(false), mIsSpeaking(false), mIsModeratorMuted(false), mLastSpokeTimestamp(0.f), mPower(0.f), mVolume(0), mServiceType(serviceTypeUnknown), - mOnMuteList(false), mUserVolume(100), mVolumeDirty(false), mAvatarIDValid(false) + mURI(uri), + mPTT(false), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLastSpokeTimestamp(0.f), + mPower(0.f), + mVolume(0), + mOnMuteList(false), + mUserVolume(100), + mVolumeDirty(false), + mAvatarIDValid(false), + mIsSelf(false) { } -LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri) +LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(const std::string &uri) { participantState *result = NULL; - - participantMap::iterator iter = mParticipantMap.find(uri); + bool useAlternateURI = false; - if(iter != mParticipantMap.end()) + // Note: this is mostly the body of LLVoiceClient::sessionState::findParticipant(), but since we need to know if it + // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. { - // Found a matching participant already in the map. - result = iter->second; - } + participantMap::iterator iter = mParticipantsByURI.find(&uri); + if(iter == mParticipantsByURI.end()) + { + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) + { + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Use mSIPURI instead, since it will be properly encoded. + iter = mParticipantsByURI.find(&(mSIPURI)); + useAlternateURI = true; + } + } + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + } + if(!result) { // participant isn't already in one list or the other. - result = new participantState(uri); - mParticipantMap.insert(participantMap::value_type(uri, result)); - mParticipantMapChanged = true; + result = new participantState(useAlternateURI?mSIPURI:uri); + mParticipantsByURI.insert(participantMap::value_type(&(result->mURI), result)); + mParticipantsChanged = true; // Try to do a reverse transform on the URI to get the GUID back. { LLUUID id; - if(IDFromName(uri, id)) + if(IDFromName(result->mURI, id)) { result->mAvatarIDValid = true; result->mAvatarID = id; - updateMuteState(result); + if(result->updateMuteState()) + mVolumeDirty = true; + } + else + { + // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. + // This tells both code in LLVoiceClient and code in llfloateractivespeakers.cpp that the ID will not be in the name cache. + setUUIDFromStringHash(result->mAvatarID, uri); } } + mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result)); + LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; } return result; } -void LLVoiceClient::updateMuteState(participantState *p) +bool LLVoiceClient::participantState::updateMuteState() { - if(p->mAvatarIDValid) + bool result = false; + + if(mAvatarIDValid) { - bool isMuted = LLMuteList::getInstance()->isMuted(p->mAvatarID, LLMute::flagVoiceChat); - if(p->mOnMuteList != isMuted) + bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); + if(mOnMuteList != isMuted) { - p->mOnMuteList = isMuted; - p->mVolumeDirty = true; + mOnMuteList = isMuted; mVolumeDirty = true; + result = true; } } + return result; } -void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant) +void LLVoiceClient::sessionState::removeParticipant(LLVoiceClient::participantState *participant) { if(participant) { - participantMap::iterator iter = mParticipantMap.find(participant->mURI); - + participantMap::iterator iter = mParticipantsByURI.find(&(participant->mURI)); + participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(&(participant->mAvatarID)); + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - - mParticipantMap.erase(iter); - delete participant; - mParticipantMapChanged = true; + + if(iter == mParticipantsByURI.end()) + { + LL_ERRS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; + } + else if(iter2 == mParticipantsByUUID.end()) + { + LL_ERRS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; + } + else if(iter->second != iter2->second) + { + LL_ERRS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; + } + else + { + mParticipantsByURI.erase(iter); + mParticipantsByUUID.erase(iter2); + + delete participant; + mParticipantsChanged = true; + } } } -void LLVoiceClient::removeAllParticipants() +void LLVoiceClient::sessionState::removeAllParticipants() { LL_DEBUGS("Voice") << "called" << LL_ENDL; - while(!mParticipantMap.empty()) + while(!mParticipantsByURI.empty()) { - removeParticipant(mParticipantMap.begin()->second); + removeParticipant(mParticipantsByURI.begin()->second); + } + + if(!mParticipantsByUUID.empty()) + { + LL_ERRS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL } } LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void) { - return &mParticipantMap; + participantMap *result = NULL; + if(mAudioSession) + { + result = &(mAudioSession->mParticipantsByURI); + } + return result; } -LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri) +LLVoiceClient::participantState *LLVoiceClient::sessionState::findParticipant(const std::string &uri) { participantState *result = NULL; - // Don't find any participants if we're not connected. This is so that we don't continue to get stale data - // after the daemon dies. - if(mConnected) + participantMap::iterator iter = mParticipantsByURI.find(&uri); + + if(iter == mParticipantsByURI.end()) { - participantMap::iterator iter = mParticipantMap.find(uri); - - if(iter != mParticipantMap.end()) + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) { - result = iter->second; + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Look up the other URI + iter = mParticipantsByURI.find(&(mSIPURI)); } } - + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + return result; } - -LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar) +LLVoiceClient::participantState* LLVoiceClient::sessionState::findParticipantByID(const LLUUID& id) { participantState * result = NULL; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(&id); - // You'd think this would work, but it doesn't... -// std::string uri = sipURIFromAvatar(avatar); - - // Currently, the URI is just the account name. - std::string loginName = nameFromAvatar(avatar); - result = findParticipant(loginName); - - if(result != NULL) + if(iter != mParticipantsByUUID.end()) { - if(!result->mAvatarIDValid) - { - result->mAvatarID = avatar->getID(); - result->mAvatarIDValid = true; - - // We just figured out the avatar ID, so the participant list has "changed" from the perspective of anyone who uses that to identify participants. - mParticipantMapChanged = true; - - updateMuteState(result); - } - - + result = iter->second; } return result; @@ -3194,43 +4941,19 @@ LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvat LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id) { participantState * result = NULL; - - // Currently, the URI is just the account name. - std::string loginName = nameFromID(id); - result = findParticipant(loginName); - - return result; -} - - -void LLVoiceClient::clearChannelMap(void) -{ - mChannelMap.clear(); -} - -void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri) -{ - LL_DEBUGS("Voice") << "Adding channel name mapping: " << name << " -> " << uri << LL_ENDL; - mChannelMap.insert(channelMap::value_type(name, uri)); -} - -std::string LLVoiceClient::findChannelURI(std::string &name) -{ - std::string result; - channelMap::iterator iter = mChannelMap.find(name); - - if(iter != mChannelMap.end()) + if(mAudioSession) { - result = iter->second; + result = mAudioSession->findParticipantByID(id); } return result; } + void LLVoiceClient::parcelChanged() { - if(getState() >= stateLoggedIn) + if(getState() >= stateNoChannel) { // If the user is logged in, start a channel lookup. LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; @@ -3244,7 +4967,7 @@ void LLVoiceClient::parcelChanged() } else { - // The transition to stateLoggedIn needs to kick this off again. + // The transition to stateNoChannel needs to kick this off again. LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL; } } @@ -3252,12 +4975,17 @@ void LLVoiceClient::parcelChanged() void LLVoiceClient::switchChannel( std::string uri, bool spatial, - bool noReconnect, + bool no_reconnect, + bool is_p2p, std::string hash) { bool needsSwitch = false; - LL_DEBUGS("Voice") << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << LL_ENDL; + LL_DEBUGS("Voice") + << "called in state " << state2string(getState()) + << " with uri \"" << uri << "\"" + << (spatial?", spatial is true":", spatial is false") + << LL_ENDL; switch(getState()) { @@ -3267,41 +4995,73 @@ void LLVoiceClient::switchChannel( // Always switch to the new URI from these states. needsSwitch = true; break; - + default: if(mSessionTerminateRequested) { // If a terminate has been requested, we need to compare against where the URI we're already headed to. - if(mNextSessionURI != uri) - needsSwitch = true; + if(mNextAudioSession) + { + if(mNextAudioSession->mSIPURI != uri) + needsSwitch = true; + } + else + { + // mNextAudioSession is null -- this probably means we're on our way back to spatial. + if(!uri.empty()) + { + // We do want to process a switch in this case. + needsSwitch = true; + } + } } else { // Otherwise, compare against the URI we're in now. - if(mSessionURI != uri) - needsSwitch = true; + if(mAudioSession) + { + if(mAudioSession->mSIPURI != uri) + { + needsSwitch = true; + } + } + else + { + if(!uri.empty()) + { + // mAudioSession is null -- it's not clear what case would cause this. + // For now, log it as a warning and see if it ever crops up. + LL_WARNS("Voice") << "No current audio session." << LL_ENDL; + } + } } break; } if(needsSwitch) { - mNextSessionURI = uri; - mNextSessionHash = hash; - mNextSessionHandle.clear(); - mNextP2PSessionURI.clear(); - mNextSessionSpatial = spatial; - mNextSessionNoReconnect = noReconnect; - if(uri.empty()) { // Leave any channel we may be in LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; + + sessionState *oldSession = mNextAudioSession; + mNextAudioSession = NULL; + + // The old session may now need to be deleted. + reapSession(oldSession); + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); } else { LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; + + mNextAudioSession = addSession(uri); + mNextAudioSession->mHash = hash; + mNextAudioSession->mIsSpatial = spatial; + mNextAudioSession->mReconnect = !no_reconnect; + mNextAudioSession->mIsP2P = is_p2p; } if(getState() <= stateNoChannel) @@ -3316,15 +5076,10 @@ void LLVoiceClient::switchChannel( } } -void LLVoiceClient::joinSession(std::string handle, std::string uri) +void LLVoiceClient::joinSession(sessionState *session) { - mNextSessionURI.clear(); - mNextSessionHash.clear(); - mNextP2PSessionURI = uri; - mNextSessionHandle = handle; - mNextSessionSpatial = false; - mNextSessionNoReconnect = false; - + mNextAudioSession = session; + if(getState() <= stateNoChannel) { // We're already set up to join a channel, just needed to fill in the session handle @@ -3340,7 +5095,7 @@ void LLVoiceClient::setNonSpatialChannel( const std::string &uri, const std::string &credentials) { - switchChannel(uri, false, false, credentials); + switchChannel(uri, false, false, false, credentials); } void LLVoiceClient::setSpatialChannel( @@ -3348,51 +5103,216 @@ void LLVoiceClient::setSpatialChannel( const std::string &credentials) { mSpatialSessionURI = uri; + mSpatialSessionCredentials = credentials; mAreaVoiceDisabled = mSpatialSessionURI.empty(); LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - if(mNonSpatialChannel || !mNextSessionSpatial) + if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) { // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; } else { - switchChannel(mSpatialSessionURI, true, false, credentials); + switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); } } -void LLVoiceClient::callUser(LLUUID &uuid) +void LLVoiceClient::callUser(const LLUUID &uuid) { std::string userURI = sipURIFromID(uuid); - switchChannel(userURI, false, true); + switchChannel(userURI, false, true, true); +} + +LLVoiceClient::sessionState* LLVoiceClient::startUserIMSession(const LLUUID &uuid) +{ + // Figure out if a session with the user already exists + sessionState *session = findSession(uuid); + if(!session) + { + // No session with user, need to start one. + std::string uri = sipURIFromID(uuid); + session = addSession(uri); + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + session->mCallerID = uuid; + } + + if(session) + { + if(session->mHandle.empty()) + { + // Session isn't active -- start it up. + sessionCreateSendMessage(session, false, true); + } + else + { + // Session is already active -- start up text. + sessionTextConnectSendMessage(session); + } + } + + return session; } -void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id) +bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) { - joinSession(sessionHandle, sipURIFromID(other_user_id)); + bool result = false; + + // Attempt to locate the indicated session + sessionState *session = startUserIMSession(participant_id); + if(session) + { + // found the session, attempt to send the message + session->mTextMsgQueue.push(message); + + // Try to send queued messages (will do nothing if the session is not open yet) + sendQueuedTextMessages(session); + + // The message is queued, so we succeed. + result = true; + } + else + { + LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL; + } + + return result; +} + +void LLVoiceClient::sendQueuedTextMessages(sessionState *session) +{ + if(session->mTextStreamState == 1) + { + if(!session->mTextMsgQueue.empty()) + { + std::ostringstream stream; + + while(!session->mTextMsgQueue.empty()) + { + std::string message = session->mTextMsgQueue.front(); + session->mTextMsgQueue.pop(); + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SendMessage.1\">" + << "<SessionHandle>" << session->mHandle << "</SessionHandle>" + << "<MessageHeader>text/HTML</MessageHeader>" + << "<MessageBody>" << message << "</MessageBody>" + << "</Request>" + << "\n\n\n"; + } + writeString(stream.str()); + } + } + else + { + // Session isn't connected yet, defer until later. + } +} + +void LLVoiceClient::endUserIMSession(const LLUUID &uuid) +{ + // Figure out if a session with the user exists + sessionState *session = findSession(uuid); + if(session) + { + // found the session + if(!session->mHandle.empty()) + { + sessionTextDisconnectSendMessage(session); + } + } + else + { + LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; + } +} + +bool LLVoiceClient::answerInvite(std::string &sessionHandle) +{ + // this is only ever used to answer incoming p2p call invites. + + sessionState *session = findSession(sessionHandle); + if(session) + { + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + + joinSession(session); + return true; + } + + return false; +} + +bool LLVoiceClient::isOnlineSIP(const LLUUID &id) +{ + bool result = false; + buddyListEntry *buddy = findBuddy(id); + if(buddy) + { + result = buddy->mOnlineSLim; + LL_DEBUGS("Voice") << "Buddy " << buddy->mDisplayName << " is SIP " << (result?"online":"offline") << LL_ENDL; + } + + if(!result) + { + // This user isn't on the buddy list or doesn't show online status through the buddy list, but could be a participant in an existing session if they initiated a text IM. + sessionState *session = findSession(id); + if(session && !session->mHandle.empty()) + { + if((session->mTextStreamState != streamStateUnknown) || (session->mMediaStreamState > streamStateIdle)) + { + LL_DEBUGS("Voice") << "Open session with " << id << " found, returning SIP online state" << LL_ENDL; + // we have a p2p text session open with this user, so by definition they're online. + result = true; + } + } + } + + return result; } void LLVoiceClient::declineInvite(std::string &sessionHandle) { - sessionTerminateByHandle(sessionHandle); + sessionState *session = findSession(sessionHandle); + if(session) + { + sessionMediaDisconnectSendMessage(session); + } } void LLVoiceClient::leaveNonSpatialChannel() { - switchChannel(mSpatialSessionURI); + LL_DEBUGS("Voice") + << "called in state " << state2string(getState()) + << LL_ENDL; + + // Make sure we don't rejoin the current session. + sessionState *oldNextSession = mNextAudioSession; + mNextAudioSession = NULL; + + // Most likely this will still be the current session at this point, but check it anyway. + reapSession(oldNextSession); + + verifySessionState(); + + sessionTerminate(); } std::string LLVoiceClient::getCurrentChannel() { + std::string result; + if((getState() == stateRunning) && !mSessionTerminateRequested) { - return mSessionURI; + result = getAudioSessionURI(); } - return ""; + return result; } bool LLVoiceClient::inProximalChannel() @@ -3401,7 +5321,7 @@ bool LLVoiceClient::inProximalChannel() if((getState() == stateRunning) && !mSessionTerminateRequested) { - result = !mNonSpatialChannel; + result = inSpatialChannel(); } return result; @@ -3413,7 +5333,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id) result = "sip:"; result += nameFromID(id); result += "@"; - result += mAccountServerName; + result += mVoiceSIPURIHostName; return result; } @@ -3426,7 +5346,7 @@ std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) result = "sip:"; result += nameFromID(avatar->getID()); result += "@"; - result += mAccountServerName; + result += mVoiceSIPURIHostName; } return result; @@ -3445,6 +5365,13 @@ std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar) std::string LLVoiceClient::nameFromID(const LLUUID &uuid) { std::string result; + + if (uuid.isNull()) { + //VIVOX, the uuid emtpy look for the mURIString and return that instead. + //result.assign(uuid.mURIStringName); + LLStringUtil::replaceChar(result, '_', ' '); + return result; + } // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code. result = "x"; @@ -3458,13 +5385,24 @@ std::string LLVoiceClient::nameFromID(const LLUUID &uuid) // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') + // The reverse transform can be done with: + // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p + return result; } -bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid) +bool LLVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) { bool result = false; + // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" + // If it is, convert to a bare name before doing the transform. + std::string name = nameFromsipURI(inName); + + // Doesn't look like a SIP URI, assume it's an actual name. + if(name.empty()) + name = inName; + // This will only work if the name is of the proper form. // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: // "xFnPP04IpREWNkuw1cOXlhw==" @@ -3486,6 +5424,13 @@ bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid) memcpy(uuid.mData, rawuuid, UUID_BYTES); result = true; } + } + + if(!result) + { + // VIVOX: not a standard account name, just copy the URI name mURIString field + // and hope for the best. bpj + uuid.setNull(); // VIVOX, set the uuid field to nulls } return result; @@ -3502,13 +5447,59 @@ std::string LLVoiceClient::sipURIFromName(std::string &name) result = "sip:"; result += name; result += "@"; - result += mAccountServerName; + result += mVoiceSIPURIHostName; // LLStringUtil::toLower(result); return result; } +std::string LLVoiceClient::nameFromsipURI(const std::string &uri) +{ + std::string result; + + std::string::size_type sipOffset, atOffset; + sipOffset = uri.find("sip:"); + atOffset = uri.find("@"); + if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) + { + result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); + } + + return result; +} + +bool LLVoiceClient::inSpatialChannel(void) +{ + bool result = false; + + if(mAudioSession) + result = mAudioSession->mIsSpatial; + + return result; +} + +std::string LLVoiceClient::getAudioSessionURI() +{ + std::string result; + + if(mAudioSession) + result = mAudioSession->mSIPURI; + + return result; +} + +std::string LLVoiceClient::getAudioSessionHandle() +{ + std::string result; + + if(mAudioSession) + result = mAudioSession->mHandle; + + return result; +} + + ///////////////////////////// // Sending updates of current state @@ -3547,7 +5538,8 @@ void LLVoiceClient::updatePosition(void) LLMatrix3 rot; LLVector3d pos; - // MBW -- XXX -- Setting both camera and avatar velocity to 0 for now. May figure it out later... + // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... + // They're currently always set to zero. // Send the current camera position to the voice code rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); @@ -3562,7 +5554,7 @@ void LLVoiceClient::updatePosition(void) rot = agent->getRootJoint()->getWorldRotation().getMatrix3(); pos = agent->getPositionGlobal(); - // MBW -- XXX -- Can we get the head offset from outside the LLVOAvatar? + // TODO: Can we get the head offset from outside the LLVOAvatar? // pos += LLVector3d(mHeadOffset); pos += LLVector3d(0.f, 0.f, 1.f); @@ -3668,8 +5660,8 @@ void LLVoiceClient::setVoiceEnabled(bool enabled) } else { - // for now, leave active channel, to auto join when turning voice back on - //LLVoiceChannel::getCurrentVoiceChannel->deactivate(); + // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. + LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); } } } @@ -3749,56 +5741,34 @@ void LLVoiceClient::setEarLocation(S32 loc) void LLVoiceClient::setVoiceVolume(F32 volume) { - LL_DEBUGS("Voice") << "volume is " << volume << LL_ENDL; + int scaled_volume = scale_speaker_volume(volume); - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50 - - volume -= 0.5f; // offset volume to the range [-0.5 ... 0.5], with 0 at the default. - int scaledVolume = 24; // offset scaledVolume by its default level - if(volume < 0.0f) - scaledVolume += ((int)(volume * 248.0f)); // (24 - (-100)) * 2 - else - scaledVolume += ((int)(volume * 52.0f)); // (50 - 24) * 2 - - if(scaledVolume != mSpeakerVolume) + if(scaled_volume != mSpeakerVolume) { - if((scaledVolume == -100) || (mSpeakerVolume == -100)) + if((scaled_volume == 0) || (mSpeakerVolume == 0)) { mSpeakerMuteDirty = true; } - mSpeakerVolume = scaledVolume; + mSpeakerVolume = scaled_volume; mSpeakerVolumeDirty = true; } } void LLVoiceClient::setMicGain(F32 volume) { - int scaledVolume = ((int)(volume * 100.0f)) - 100; - if(scaledVolume != mMicVolume) + int scaled_volume = scale_mic_volume(volume); + + if(scaled_volume != mMicVolume) { - mMicVolume = scaledVolume; + mMicVolume = scaled_volume; mMicVolumeDirty = true; } } -void LLVoiceClient::setVivoxDebugServerName(std::string &serverName) -{ - if(!mAccountServerName.empty()) - { - // The name has been filled in already, which means we know whether we're connecting to agni or not. - if(!sConnectingToAgni) - { - // Only use the setting if we're connecting to a development grid -- always use bhr when on agni. - mAccountServerName = serverName; - } - } -} - void LLVoiceClient::keyDown(KEY key, MASK mask) { - LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL; +// LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL; if (gKeyboard->getKeyRepeated(key)) { @@ -3936,19 +5906,6 @@ BOOL LLVoiceClient::getUsingPTT(const LLUUID& id) return result; } -BOOL LLVoiceClient::getPTTPressed(const LLUUID& id) -{ - BOOL result = FALSE; - - participantState *participant = findParticipantByID(id); - if(participant) - { - result = participant->mPTT; - } - - return result; -} - BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) { BOOL result = FALSE; @@ -3980,144 +5937,849 @@ F32 LLVoiceClient::getUserVolume(const LLUUID& id) void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { + if(mAudioSession) + { + participantState *participant = findParticipantByID(id); + if (participant) + { + // volume can amplify by as much as 4x! + S32 ivol = (S32)(400.f * volume * volume); + participant->mUserVolume = llclamp(ivol, 0, 400); + participant->mVolumeDirty = TRUE; + mAudioSession->mVolumeDirty = TRUE; + } + } +} + +std::string LLVoiceClient::getGroupID(const LLUUID& id) +{ + std::string result; + participantState *participant = findParticipantByID(id); - if (participant) + if(participant) { - // volume can amplify by as much as 4x! - S32 ivol = (S32)(400.f * volume * volume); - participant->mUserVolume = llclamp(ivol, 0, 400); - participant->mVolumeDirty = TRUE; - mVolumeDirty = TRUE; + result = participant->mGroupID; } + + return result; } +BOOL LLVoiceClient::getAreaVoiceDisabled() +{ + return mAreaVoiceDisabled; +} + +void LLVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; + + if(!mMainSessionGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" + << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" + << "<RecordingControlType>Start</RecordingControlType>" + << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>" + << "<Filename>" << "" << "</Filename>" + << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>" + << "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>" + << "</Request>\n\n\n"; -LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id) + writeString(stream.str()); + } +} + +void LLVoiceClient::recordingLoopSave(const std::string& filename) { - serviceType result = serviceTypeUnknown; +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; - participantState *participant = findParticipantByID(id); - if(participant) + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) { - result = participant->mServiceType; + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" + << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" + << "<RecordingControlType>Flush</RecordingControlType>" + << "<Filename>" << filename << "</Filename>" + << "</Request>\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVoiceClient::recordingStop() +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" + << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" + << "<RecordingControlType>Stop</RecordingControlType>" + << "</Request>\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVoiceClient::filePlaybackStart(const std::string& filename) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" + << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" + << "<RecordingControlType>Start</RecordingControlType>" + << "<Filename>" << filename << "</Filename>" + << "</Request>\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVoiceClient::filePlaybackStop() +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" + << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" + << "<RecordingControlType>Stop</RecordingControlType>" + << "</Request>\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVoiceClient::filePlaybackSetPaused(bool paused) +{ + // TODO: Implement once Vivox gives me a sample +} + +void LLVoiceClient::filePlaybackSetMode(bool vox, float speed) +{ + // TODO: Implement once Vivox gives me a sample +} + +LLVoiceClient::sessionState::sessionState() : + mMediaStreamState(streamStateUnknown), + mTextStreamState(streamStateUnknown), + mCreateInProgress(false), + mMediaConnectInProgress(false), + mVoiceInvitePending(false), + mTextInvitePending(false), + mSynthesizedCallerID(false), + mIsChannel(false), + mIsSpatial(false), + mIsP2P(false), + mIncoming(false), + mVoiceEnabled(false), + mReconnect(false), + mVolumeDirty(false), + mParticipantsChanged(false) +{ +} + +LLVoiceClient::sessionState::~sessionState() +{ + removeAllParticipants(); +} + +LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void) +{ + return mSessions.begin(); +} + +LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void) +{ + return mSessions.end(); +} + + +LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle) +{ + sessionState *result = NULL; + sessionMap::iterator iter = mSessionsByHandle.find(&handle); + if(iter != mSessionsByHandle.end()) + { + result = iter->second; } return result; } -std::string LLVoiceClient::getGroupID(const LLUUID& id) +LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) +{ + sessionState *result = NULL; + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + if(session->mCreateInProgress && (session->mSIPURI == uri)) + { + result = session; + break; + } + } + + return result; +} + +LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id) { - std::string result; + sessionState *result = NULL; + + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + if(session->mCallerID == participant_id) + { + result = session; + break; + } + } + + return result; +} - participantState *participant = findParticipantByID(id); - if(participant) +LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle) +{ + sessionState *result = NULL; + + if(handle.empty()) { - result = participant->mGroupID; + // No handle supplied. + // Check whether there's already a session with this URI + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *s = *iter; + if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri)) + { + // TODO: I need to think about this logic... it's possible that this case should raise an internal error. + result = s; + break; + } + } + } + else // (!handle.empty()) + { + // Check for an existing session with this handle + sessionMap::iterator iter = mSessionsByHandle.find(&handle); + + if(iter != mSessionsByHandle.end()) + { + result = iter->second; + } + } + + if(!result) + { + // No existing session found. + + LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL; + result = new sessionState(); + result->mSIPURI = uri; + result->mHandle = handle; + + mSessions.insert(result); + + if(!result->mHandle.empty()) + { + mSessionsByHandle.insert(sessionMap::value_type(&(result->mHandle), result)); + } + } + else + { + // Found an existing session + + if(uri != result->mSIPURI) + { + // TODO: Should this be an internal error? + LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; + setSessionURI(result, uri); + } + + if(handle != result->mHandle) + { + if(handle.empty()) + { + // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. + LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; + } + else + { + // TODO: Should this be an internal error? + LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; + setSessionHandle(result, handle); + } + } + + LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; + } + + verifySessionState(); + + return result; +} + +void LLVoiceClient::setSessionHandle(sessionState *session, const std::string &handle) +{ + // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. + + if(!session->mHandle.empty()) + { + // Remove session from the map if it should have been there. + sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle)); + if(iter != mSessionsByHandle.end()) + { + if(iter->second != session) + { + LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL; + } + + mSessionsByHandle.erase(iter); + } + else + { + LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL; + } + } + + session->mHandle = handle; + + if(!handle.empty()) + { + mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session)); + } + + verifySessionState(); +} + +void LLVoiceClient::setSessionURI(sessionState *session, const std::string &uri) +{ + // There used to be a map of session URIs to sessions, which made this complex.... + session->mSIPURI = uri; + + verifySessionState(); +} + +void LLVoiceClient::deleteSession(sessionState *session) +{ + // Remove the session from the handle map + if(!session->mHandle.empty()) + { + sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle)); + if(iter != mSessionsByHandle.end()) + { + if(iter->second != session) + { + LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL + } + mSessionsByHandle.erase(iter); + } + } + + // Remove the session from the URI map + mSessions.erase(session); + + // At this point, the session should be unhooked from all lists and all state should be consistent. + verifySessionState(); + + // If this is the current audio session, clean up the pointer which will soon be dangling. + if(mAudioSession == session) + { + mAudioSession = NULL; + mAudioSessionChanged = true; + } + + // ditto for the next audio session + if(mNextAudioSession == session) + { + mNextAudioSession = NULL; + } + + // delete the session + delete session; +} + +void LLVoiceClient::deleteAllSessions() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while(!mSessions.empty()) + { + deleteSession(*(sessionsBegin())); + } + + if(!mSessionsByHandle.empty()) + { + LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL + } +} + +void LLVoiceClient::verifySessionState(void) +{ + // This is mostly intended for debugging problems with session state management. + LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL; + + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + + LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL; + + if(!session->mHandle.empty()) + { + // every session with a non-empty handle needs to be in the handle map + sessionMap::iterator i2 = mSessionsByHandle.find(&(session->mHandle)); + if(i2 == mSessionsByHandle.end()) + { + LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL; + } + else + { + if(i2->second != session) + { + LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL; + } + } + } + } + + // check that every entry in the handle map points to a valid session in the session set + for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++) + { + sessionState *session = iter->second; + sessionIterator i2 = mSessions.find(session); + if(i2 == mSessions.end()) + { + LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL; + } + else + { + if(session->mHandle != (*i2)->mHandle) + { + LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL; + } + } + } +} + +LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) : + mURI(uri) +{ + mOnlineSL = false; + mOnlineSLim = false; + mCanSeeMeOnline = true; + mHasBlockListEntry = false; + mHasAutoAcceptListEntry = false; + mNameResolved = false; + mInVivoxBuddies = false; + mInSLFriends = false; + mNeedsNameUpdate = false; +} + +void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName) +{ + buddyListEntry *buddy = addBuddy(uri, displayName); + buddy->mInVivoxBuddies = true; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri) +{ + std::string empty; + buddyListEntry *buddy = addBuddy(uri, empty); + if(buddy->mDisplayName.empty()) + { + buddy->mNameResolved = false; + } + return buddy; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter = mBuddyListMap.find(&uri); + + if(iter != mBuddyListMap.end()) + { + // Found a matching buddy already in the map. + LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL; + result = iter->second; + } + + if(!result) + { + // participant isn't already in one list or the other. + LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL; + result = new buddyListEntry(uri); + result->mDisplayName = displayName; + + if(IDFromName(uri, result->mUUID)) + { + // Extracted UUID from name successfully. + } + else + { + LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL; + } + + mBuddyListMap.insert(buddyListMap::value_type(&(result->mURI), result)); } return result; } -BOOL LLVoiceClient::getAreaVoiceDisabled() +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri) { - return mAreaVoiceDisabled; + buddyListEntry *result = NULL; + buddyListMap::iterator iter = mBuddyListMap.find(&uri); + if(iter != mBuddyListMap.end()) + { + result = iter->second; + } + + return result; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter; + + for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) + { + if(iter->second->mUUID == id) + { + result = iter->second; + break; + } + } + + return result; +} + +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter; + + for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) + { + if(iter->second->mDisplayName == name) + { + result = iter->second; + break; + } + } + + return result; +} + +void LLVoiceClient::deleteBuddy(const std::string &uri) +{ + buddyListMap::iterator iter = mBuddyListMap.find(&uri); + if(iter != mBuddyListMap.end()) + { + LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL; + buddyListEntry *buddy = iter->second; + mBuddyListMap.erase(iter); + delete buddy; + } + else + { + LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL; + } + +} + +void LLVoiceClient::deleteAllBuddies(void) +{ + while(!mBuddyListMap.empty()) + { + deleteBuddy(*(mBuddyListMap.begin()->first)); + } + + // Don't want to correlate with friends list when we've emptied the buddy list. + mBuddyListMapPopulated = false; + + // Don't want to correlate with friends list when we've reset the block rules. + mBlockRulesListReceived = false; + mAutoAcceptRulesListReceived = false; +} + +void LLVoiceClient::deleteAllBlockRules(void) +{ + // Clear the block list entry flags from all local buddy list entries + buddyListMap::iterator buddy_it; + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + { + buddy_it->second->mHasBlockListEntry = false; + } +} + +void LLVoiceClient::deleteAllAutoAcceptRules(void) +{ + // Clear the auto-accept list entry flags from all local buddy list entries + buddyListMap::iterator buddy_it; + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + { + buddy_it->second->mHasAutoAcceptListEntry = false; + } +} + +void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly) +{ + buddyListEntry *buddy = NULL; + + // blockMask is the SIP URI of a friends list entry + buddyListMap::iterator iter = mBuddyListMap.find(&blockMask); + if(iter != mBuddyListMap.end()) + { + LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL; + buddy = iter->second; + } + + if(buddy == NULL) + { + LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL; + buddy = addBuddy(blockMask); + } + + if(buddy != NULL) + { + buddy->mHasBlockListEntry = true; + } +} + +void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy) +{ + buddyListEntry *buddy = NULL; + + // blockMask is the SIP URI of a friends list entry + buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask); + if(iter != mBuddyListMap.end()) + { + LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL; + buddy = iter->second; + } + + if(buddy == NULL) + { + LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL; + buddy = addBuddy(autoAcceptMask); + } + + if(buddy != NULL) + { + buddy->mHasAutoAcceptListEntry = true; + } +} + +void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString) +{ + // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. + mBlockRulesListReceived = true; +} + +void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString) +{ + // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. + mAutoAcceptRulesListReceived = true; } void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - mObservers.insert(observer); + mParticipantObservers.insert(observer); } void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - mObservers.erase(observer); + mParticipantObservers.erase(observer); } -void LLVoiceClient::notifyObservers() +void LLVoiceClient::notifyParticipantObservers() { - for (observer_set_t::iterator it = mObservers.begin(); - it != mObservers.end(); + for (observer_set_t::iterator it = mParticipantObservers.begin(); + it != mParticipantObservers.end(); ) { LLVoiceClientParticipantObserver* observer = *it; observer->onChange(); // In case onChange() deleted an entry. - it = mObservers.upper_bound(observer); + it = mParticipantObservers.upper_bound(observer); } } -void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer) +void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { mStatusObservers.insert(observer); } -void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer) +void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { mStatusObservers.erase(observer); } void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) + if(mAudioSession) { - switch(mVivoxErrorStatusCode) + if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mVivoxErrorStatusCode = 0; - } - - if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - //NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT - && (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408)) - { - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + switch(mAudioSession->mErrorStatusCode) + { + case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; + case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; + case 20715: + //invalid channel, we may be using a set of poorly cached + //info + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + case 1009: + //invalid username and password + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + } - // Reset the error code to make sure it won't be reused later by accident. - mVivoxErrorStatusCode = 0; + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + } + else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) + { + switch(mAudioSession->mErrorStatusCode) + { + case 404: // NOT_FOUND + case 480: // TEMPORARILY_UNAVAILABLE + case 408: // REQUEST_TIMEOUT + // call failed because other user was not available + // treat this as an error case + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + break; + } + } } - - LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session URI " << mSessionURI << LL_ENDL; + + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", session URI " << getAudioSessionURI() + << (inSpatialChannel()?", proximal is true":", proximal is false") + << LL_ENDL; for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end(); ) { LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, mSessionURI, !mNonSpatialChannel); + observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); // In case onError() deleted an entry. it = mStatusObservers.upper_bound(observer); } } +void LLVoiceClient::addObserver(LLFriendObserver* observer) +{ + mFriendObservers.insert(observer); +} + +void LLVoiceClient::removeObserver(LLFriendObserver* observer) +{ + mFriendObservers.erase(observer); +} + +void LLVoiceClient::notifyFriendObservers() +{ + for (friend_observer_set_t::iterator it = mFriendObservers.begin(); + it != mFriendObservers.end(); + ) + { + LLFriendObserver* observer = *it; + it++; + // The only friend-related thing we notify on is online/offline transitions. + observer->changed(LLFriendObserver::ONLINE); + } +} + +void LLVoiceClient::lookupName(const LLUUID &id) +{ + gCacheName->getName(id, onAvatarNameLookup); +} + //static -// void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) -// { -// participantState* statep = gVoiceClient->findParticipantByID(id); +void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) +{ + if(gVoiceClient) + { + std::string name = llformat("%s %s", first.c_str(), last.c_str()); + gVoiceClient->avatarNameResolved(id, name); + } +} -// if (statep) -// { -// statep->mDisplayName = first + " " + last; -// } +void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ + // If the avatar whose name just resolved is on our friends list, resync the friends list. + if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL) + { + mFriendsListDirty = true; + } -// gVoiceClient->notifyObservers(); -// } + // Iterate over all sessions. + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + + // Check for this user as a participant in this session + participantState *participant = session->findParticipantByID(id); + if(participant) + { + // Found -- fill in the name + participant->mAccountName = name; + // and post a "participants updated" message to listeners later. + session->mParticipantsChanged = true; + } + + // Check whether this is a p2p session whose caller name just resolved + if(session->mCallerID == id) + { + // this session's "caller ID" just resolved. Fill in the name. + session->mName = name; + if(session->mTextInvitePending) + { + session->mTextInvitePending = false; + + // We don't need to call gIMMgr->addP2PSession() here. The first incoming message will create the panel. + } + if(session->mVoiceInvitePending) + { + session->mVoiceInvitePending = false; + + gIMMgr->inviteToSession( + session->mIMSessionID, + session->mName, + session->mCallerID, + session->mName, + IM_SESSION_P2P_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE, + session->mHandle, + session->mSIPURI); + } + + } + } +} class LLViewerParcelVoiceInfo : public LLHTTPNode { |