From dd437009e88954fd0fe9dd95b903dbd1ea52e901 Mon Sep 17 00:00:00 2001 From: Monroe Williams Date: Fri, 27 Feb 2009 21:01:19 +0000 Subject: svn merge -r 113014:113017 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-QAR-1323 Merging in QAR-1323. --- indra/newview/app_settings/settings.xml | 30 +- .../installers/darwin/dmg-cleanup.applescript | 28 + indra/newview/llfloaterfriends.cpp | 40 +- indra/newview/llimpanel.cpp | 88 +- indra/newview/llimpanel.h | 4 +- indra/newview/llimview.cpp | 12 +- indra/newview/llimview.h | 6 +- indra/newview/llviewercontrol.cpp | 1 - indra/newview/llvoiceclient.cpp | 5588 +++++++++++++++----- indra/newview/llvoiceclient.h | 439 +- .../skins/default/textures/slim_icon_16_viewer.tga | Bin 0 -> 1032 bytes indra/newview/viewer_manifest.py | 6 - 12 files changed, 4607 insertions(+), 1635 deletions(-) create mode 100644 indra/newview/installers/darwin/dmg-cleanup.applescript create mode 100644 indra/newview/skins/default/textures/slim_icon_16_viewer.tga (limited to 'indra/newview') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 523032bf67..e964799e4c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10198,6 +10198,17 @@ Value 0 + VivoxAutoPostCrashDumps + + Comment + If true, SLVoice will automatically send crash dumps directly to Vivox. + Persist + 1 + Type + Boolean + Value + 0 + VivoxDebugLevel Comment @@ -10209,16 +10220,27 @@ Value -1 - VivoxDebugServerName + VivoxDebugSIPURIHostName + + Comment + Hostname portion of vivox SIP URIs (empty string for the default). + Persist + 1 + Type + String + Value + + + VivoxDebugVoiceAccountServerURI Comment - Hostname of the vivox account server to use for voice when not connected to Agni. + URI to the vivox account management server (empty string for the default). Persist 1 Type String Value - bhd.vivox.com + VoiceCallsFriendsOnly @@ -10361,7 +10383,7 @@ Type U32 Value - 44124 + 44125 WLSkyDetail diff --git a/indra/newview/installers/darwin/dmg-cleanup.applescript b/indra/newview/installers/darwin/dmg-cleanup.applescript new file mode 100644 index 0000000000..f3d39aec21 --- /dev/null +++ b/indra/newview/installers/darwin/dmg-cleanup.applescript @@ -0,0 +1,28 @@ +-- First, convert the disk image to "read-write" format with Disk Utility or hdiutil +-- Mount the image, open the disk image window in the Finder and make it frontmost, then run this script from inside Script Editor +-- After running the script, unmount the disk image, re-mount it, and copy the .DS_Store file off from the command line. + +tell application "Finder" + + set foo to every item in front window + repeat with i in foo + if the name of i is "Applications" then + set the position of i to {391, 165} + else if the name of i ends with ".app" then + set the position of i to {121, 166} + end if + end repeat + + -- There doesn't seem to be a way to set the background picture with applescript, but all the saved .DS_Store files should already have that set correctly. + + set foo to front window + set current view of foo to icon view + set toolbar visible of foo to false + set statusbar visible of foo to false + set the bounds of foo to {100, 100, 600, 399} + + -- set the position of front window to {100, 100} + -- get {name, position} of every item of front window + + get properties of front window +end tell diff --git a/indra/newview/llfloaterfriends.cpp b/indra/newview/llfloaterfriends.cpp index f5133584b5..014a631a53 100644 --- a/indra/newview/llfloaterfriends.cpp +++ b/indra/newview/llfloaterfriends.cpp @@ -58,6 +58,7 @@ #include "llviewermessage.h" #include "lltimer.h" #include "lltextbox.h" +#include "llvoiceclient.h" //Maximum number of people you can select to do an operation on at once. #define MAX_FRIEND_SELECT 20 @@ -65,6 +66,8 @@ #define RIGHTS_CHANGE_TIMEOUT 5.0 #define OBSERVER_TIMEOUT 0.5 +#define ONLINE_SIP_ICON_NAME "slim_icon_16_viewer.tga" + // simple class to observe the calling cards. class LLLocalFriendsObserver : public LLFriendObserver, public LLEventTimer { @@ -112,10 +115,14 @@ LLPanelFriends::LLPanelFriends() : mEventTimer.stop(); mObserver = new LLLocalFriendsObserver(this); LLAvatarTracker::instance().addObserver(mObserver); + // For notification when SIP online status changes. + LLVoiceClient::getInstance()->addObserver(mObserver); } LLPanelFriends::~LLPanelFriends() { + // For notification when SIP online status changes. + LLVoiceClient::getInstance()->removeObserver(mObserver); LLAvatarTracker::instance().removeObserver(mObserver); delete mObserver; } @@ -213,7 +220,9 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id) LLAvatarTracker& at = LLAvatarTracker::instance(); const LLRelationship* relationInfo = at.getBuddyInfo(agent_id); if(!relationInfo) return FALSE; - BOOL online = relationInfo->isOnline(); + + bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(agent_id); + bool isOnline = relationInfo->isOnline(); std::string fullname; BOOL have_name = gCacheName->getFullName(agent_id, fullname); @@ -229,12 +238,17 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id) LLSD& online_status_column = element["columns"][LIST_ONLINE_STATUS]; online_status_column["column"] = "icon_online_status"; online_status_column["type"] = "icon"; - - if (online) + + if (isOnline) { friend_column["font-style"] = "BOLD"; online_status_column["value"] = "icon_avatar_online.tga"; } + else if(isOnlineSIP) + { + friend_column["font-style"] = "BOLD"; + online_status_column["value"] = ONLINE_SIP_ICON_NAME; + } LLSD& online_column = element["columns"][LIST_VISIBLE_ONLINE]; online_column["column"] = "icon_visible_online"; @@ -272,14 +286,30 @@ BOOL LLPanelFriends::updateFriendItem(const LLUUID& agent_id, const LLRelationsh if (!info) return FALSE; LLScrollListItem* itemp = mFriendsList->getItem(agent_id); if (!itemp) return FALSE; + + bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(itemp->getUUID()); + bool isOnline = info->isOnline(); std::string fullname; BOOL have_name = gCacheName->getFullName(agent_id, fullname); + + // Name of the status icon to use + std::string statusIcon; + + if(isOnline) + { + statusIcon = "icon_avatar_online.tga"; + } + else if(isOnlineSIP) + { + statusIcon = ONLINE_SIP_ICON_NAME; + } - itemp->getColumn(LIST_ONLINE_STATUS)->setValue(info->isOnline() ? std::string("icon_avatar_online.tga") : LLStringUtil::null); + itemp->getColumn(LIST_ONLINE_STATUS)->setValue(statusIcon); + itemp->getColumn(LIST_FRIEND_NAME)->setValue(fullname); // render name of online friends in bold text - ((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle(info->isOnline() ? LLFontGL::BOLD : LLFontGL::NORMAL); + ((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle((isOnline || isOnlineSIP) ? LLFontGL::BOLD : LLFontGL::NORMAL); itemp->getColumn(LIST_VISIBLE_ONLINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); itemp->getColumn(LIST_VISIBLE_MAP)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)); itemp->getColumn(LIST_EDIT_MINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index bded46b23b..de9e92fcdd 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -358,7 +358,7 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; } - LLVoiceClient::getInstance()->addStatusObserver(this); + LLVoiceClient::getInstance()->addObserver(this); } LLVoiceChannel::~LLVoiceChannel() @@ -366,7 +366,7 @@ LLVoiceChannel::~LLVoiceChannel() // Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed. if(gVoiceClient) { - gVoiceClient->removeStatusObserver(this); + gVoiceClient->removeObserver(this); } sVoiceChannelMap.erase(mSessionID); @@ -985,7 +985,8 @@ void LLVoiceChannelP2P::activate() // otherwise answering the call else { - LLVoiceClient::getInstance()->answerInvite(mSessionHandle, mOtherUserID); + LLVoiceClient::getInstance()->answerInvite(mSessionHandle); + // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. mSessionHandle.clear(); } @@ -1002,7 +1003,7 @@ void LLVoiceChannelP2P::getChannelInfo() } // receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle) +void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) { BOOL needs_activate = FALSE; if (callStarted()) @@ -1025,8 +1026,17 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle) } mSessionHandle = handle; + // The URI of a p2p session should always be the other end's SIP URI. - setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + if(!inURI.empty()) + { + setURI(inURI); + } + else + { + setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + } + mReceivedCall = TRUE; if (needs_activate) @@ -1209,7 +1219,23 @@ LLFloaterIMPanel::~LLFloaterIMPanel() { delete mSpeakers; mSpeakers = NULL; - + + // End the text IM session if necessary + if(gVoiceClient && mOtherParticipantUUID.notNull()) + { + switch(mDialog) + { + case IM_NOTHING_SPECIAL: + case IM_SESSION_P2P_INVITE: + gVoiceClient->endUserIMSession(mOtherParticipantUUID); + break; + + default: + // Appease the compiler + break; + } + } + //kicks you out of the voice channel if it is currently active // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). @@ -1872,33 +1898,45 @@ void deliver_message(const std::string& utf8_text, EInstantMessage dialog) { std::string name; + bool sent = false; gAgent.buildFullname(name); const LLRelationship* info = NULL; info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); + U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; - - // default to IM_SESSION_SEND unless it's nothing special - in - // which case it's probably an IM to everyone. - U8 new_dialog = dialog; - - if ( dialog != IM_NOTHING_SPECIAL ) + + if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) { - new_dialog = IM_SESSION_SEND; + // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. + sent = gVoiceClient->sendTextMessage(other_participant_id, utf8_text); } + + if(!sent) + { + // Send message normally. - pack_instant_message( - gMessageSystem, - gAgent.getID(), - FALSE, - gAgent.getSessionID(), - other_participant_id, - name, - utf8_text, - offline, - (EInstantMessage)new_dialog, - im_session_id); - gAgent.sendReliableMessage(); + // default to IM_SESSION_SEND unless it's nothing special - in + // which case it's probably an IM to everyone. + U8 new_dialog = dialog; + + if ( dialog != IM_NOTHING_SPECIAL ) + { + new_dialog = IM_SESSION_SEND; + } + pack_instant_message( + gMessageSystem, + gAgent.getID(), + FALSE, + gAgent.getSessionID(), + other_participant_id, + name.c_str(), + utf8_text.c_str(), + offline, + (EInstantMessage)new_dialog, + im_session_id); + gAgent.sendReliableMessage(); + } // If there is a mute list and this is not a group chat... if ( LLMuteList::getInstance() ) diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h index 176d11c8f9..e54cec56c7 100644 --- a/indra/newview/llimpanel.h +++ b/indra/newview/llimpanel.h @@ -162,7 +162,7 @@ public: /*virtual*/ void activate(); /*virtual*/ void getChannelInfo(); - void setSessionHandle(const std::string& handle); + void setSessionHandle(const std::string& handle, const std::string &inURI); protected: virtual void setState(EState state); @@ -295,8 +295,6 @@ private: void sendTypingState(BOOL typing); - static LLFloaterIMPanel* sInstance; - private: LLLineEditor* mInputEditor; LLViewerTextEditor* mHistoryEditor; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 2b9863819a..a90ea39265 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -364,7 +364,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) session_id = gIMMgr->addP2PSession( payload["session_name"].asString(), payload["caller_id"].asUUID(), - payload["session_handle"].asString()); + payload["session_handle"].asString(), + payload["session_uri"].asString()); LLFloaterIMPanel* im_floater = gIMMgr->findFloaterBySession( @@ -725,7 +726,8 @@ BOOL LLIMMgr::isIMSessionOpen(const LLUUID& uuid) LLUUID LLIMMgr::addP2PSession(const std::string& name, const LLUUID& other_participant_id, - const std::string& voice_session_handle) + const std::string& voice_session_handle, + const std::string& caller_uri) { LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id); @@ -733,7 +735,7 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name, if(floater) { LLVoiceChannelP2P* voice_channelp = (LLVoiceChannelP2P*)floater->getVoiceChannel(); - voice_channelp->setSessionHandle(voice_session_handle); + voice_channelp->setSessionHandle(voice_session_handle, caller_uri); } return session_id; @@ -856,7 +858,8 @@ void LLIMMgr::inviteToSession( const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, - const std::string& session_handle) + const std::string& session_handle, + const std::string& session_uri) { //ignore invites from muted residents if (LLMuteList::getInstance()->isMuted(caller_id)) @@ -898,6 +901,7 @@ void LLIMMgr::inviteToSession( payload["type"] = type; payload["inv_type"] = inv_type; payload["session_handle"] = session_handle; + payload["session_uri"] = session_uri; payload["notify_box_type"] = notify_box_type; LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id); diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 88109a9154..a4e419694d 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -98,7 +98,8 @@ public: // Creates a P2P session with the requisite handle for responding to voice calls LLUUID addP2PSession(const std::string& name, const LLUUID& other_participant_id, - const std::string& voice_session_handle); + const std::string& voice_session_handle, + const std::string& caller_uri = LLStringUtil::null); // This removes the panel referenced by the uuid, and then // restores internal consistency. The internal pointer is not @@ -112,7 +113,8 @@ public: const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, - const std::string& session_handle = LLStringUtil::null); + const std::string& session_handle = LLStringUtil::null, + const std::string& session_uri = LLStringUtil::null); //Updates a given session's session IDs. Does not open, //create or do anything new. If the old session doesn't diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 1a40da9c1f..6b99cfbeaf 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -560,7 +560,6 @@ void settings_setup_listeners() gSavedSettings.getControl("PushToTalkButton")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); gSavedSettings.getControl("PushToTalkToggle")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); gSavedSettings.getControl("VoiceEarLocation")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); - gSavedSettings.getControl("VivoxDebugServerName")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); gSavedSettings.getControl("VoiceInputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); gSavedSettings.getControl("VoiceOutputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); gSavedSettings.getControl("AudioLevelMic")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1)); diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 1d62ec4b9a..3bcc0af7d5 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -63,15 +63,20 @@ #include "llviewerwindow.h" #include "llviewercamera.h" +#include "llfloaterfriends.h" //VIVOX, inorder to refresh communicate panel +#include "llfloaterchat.h" // for LLFloaterChat::addChat() + // for base64 decoding #include "apr_base64.h" // for SHA1 hash #include "apr_sha1.h" -// If we are connecting to agni AND the user's last name is "Linden", join this channel instead of looking up the sim name. -// If we are connecting to agni and the user's last name is NOT "Linden", disable voice. -#define AGNI_LINDENS_ONLY_CHANNEL "SL" +// for MD5 hash +#include "llmd5.h" + +#define USE_SESSION_GROUPS 0 + static bool sConnectingToAgni = false; F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; @@ -91,6 +96,44 @@ const F32 UPDATE_THROTTLE_SECONDS = 0.1f; const F32 LOGIN_RETRY_SECONDS = 10.0f; const int MAX_LOGIN_RETRIES = 12; +static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str) +{ + LLMD5 md5_uuid; + md5_uuid.update((const unsigned char*)str.data(), str.size()); + md5_uuid.finalize(); + md5_uuid.raw_digest(uuid.mData); +} + +static int scale_mic_volume(float volume) +{ + // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. + // Map it as follows: 0.0 -> 40, 1.0 -> 44, 2.0 -> 75 + + volume -= 1.0f; // offset volume to the range [-1.0 ... 1.0], with 0 at the default. + int scaled_volume = 44; // offset scaled_volume by its default level + if(volume < 0.0f) + scaled_volume += ((int)(volume * 4.0f)); // (44 - 40) + else + scaled_volume += ((int)(volume * 31.0f)); // (75 - 44) + + return scaled_volume; +} + +static int scale_speaker_volume(float volume) +{ + // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. + // Map it as follows: 0.0 -> 0, 0.5 -> 62, 1.0 -> 75 + + volume -= 0.5f; // offset volume to the range [-0.5 ... 0.5], with 0 at the default. + int scaled_volume = 62; // offset scaled_volume by its default level + if(volume < 0.0f) + scaled_volume += ((int)(volume * 124.0f)); // (62 - 0) * 2 + else + scaled_volume += ((int)(volume * 26.0f)); // (75 - 62) * 2 + + return scaled_volume; +} + class LLViewerVoiceAccountProvisionResponder : public LLHTTPClient::Responder { @@ -104,12 +147,13 @@ public: { if ( mRetries > 0 ) { + LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision( mRetries - 1); } else { - //TODO: throw an error message? + LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; if ( gVoiceClient ) gVoiceClient->giveUp(); } } @@ -118,9 +162,23 @@ public: { if ( gVoiceClient ) { + std::string voice_sip_uri_hostname; + std::string voice_account_server_uri; + + LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; + + if(content.has("voice_sip_uri_hostname")) + voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString(); + + // this key is actually misnamed -- it will be an entire URI, not just a hostname. + if(content.has("voice_account_server_name")) + voice_account_server_uri = content["voice_account_server_name"].asString(); + gVoiceClient->login( content["username"].asString(), - content["password"].asString()); + content["password"].asString(), + voice_sip_uri_hostname, + voice_account_server_uri); } } @@ -167,21 +225,27 @@ protected: int ignoreDepth; // Members for processing responses. The values are transient and only valid within a call to processResponse(). + bool squelchDebugOutput; int returnCode; int statusCode; std::string statusString; - std::string uuidString; + std::string requestId; std::string actionString; std::string connectorHandle; + std::string versionID; std::string accountHandle; std::string sessionHandle; - std::string eventSessionHandle; + std::string sessionGroupHandle; + std::string alias; + std::string applicationString; // Members for processing events. The values are transient and only valid within a call to processResponse(). std::string eventTypeString; int state; std::string uriString; bool isChannel; + bool incoming; + bool enabled; std::string nameString; std::string audioMediaString; std::string displayNameString; @@ -191,6 +255,21 @@ protected: bool isSpeaking; int volume; F32 energy; + std::string messageHeader; + std::string messageBody; + std::string notificationType; + bool hasText; + bool hasAudio; + bool hasVideo; + bool terminated; + std::string blockMask; + std::string presenceOnly; + std::string autoAcceptMask; + std::string autoAddAsBuddy; + int numberOfAliases; + std::string subscriptionHandle; + std::string subscriptionType; + // Members for processing text between tags std::string textBuffer; @@ -223,8 +302,6 @@ void LLVivoxProtocolParser::reset() responseDepth = 0; ignoringTags = false; accumulateText = false; - textBuffer.clear(); - energy = 0.f; ignoreDepth = 0; isChannel = false; @@ -233,10 +310,15 @@ void LLVivoxProtocolParser::reset() isModeratorMuted = false; isSpeaking = false; participantType = 0; - returnCode = 0; + squelchDebugOutput = false; + returnCode = -1; state = 0; statusCode = 0; volume = 0; + textBuffer.clear(); + alias.clear(); + numberOfAliases = 0; + applicationString.clear(); } //virtual @@ -263,33 +345,11 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( mInput.append(buf, istr.gcount()); } - // MBW -- XXX -- This should no longer be necessary. Or even possible. - // We've read all the data out of the buffer. Make sure it doesn't accumulate. -// buffer->clear(); - // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. int start = 0; int delim; while((delim = mInput.find("\n\n\n", start)) != std::string::npos) { - // Turn this on to log incoming XML - if(0) - { - int foo = mInput.find("Set3DPosition", start); - int bar = mInput.find("ParticipantPropertiesEvent", start); - if(foo != std::string::npos && (foo < delim)) - { - // This is a Set3DPosition response. Don't print it, since these are way too spammy. - } - else if(bar != std::string::npos && (bar < delim)) - { - // This is a ParticipantPropertiesEvent response. Don't print it, since these are way too spammy. - } - else - { - LL_INFOS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; - } - } // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) reset(); @@ -300,13 +360,19 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( XML_SetUserData(parser, this); XML_Parse(parser, mInput.data() + start, delim - start, false); + // If this message isn't set to be squelched, output the raw XML received. + if(!squelchDebugOutput) + { + LL_DEBUGS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; + } + start = delim + 3; } if(start != 0) mInput = mInput.substr(start); - LL_DEBUGS("Voice") << "at end, mInput is: " << mInput << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; if(!gVoiceClient->mConnected) { @@ -361,9 +427,9 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) if (responseDepth == 0) { - isEvent = strcmp("Event", tag) == 0; + isEvent = !stricmp("Event", tag); - if (strcmp("Response", tag) == 0 || isEvent) + if (!stricmp("Response", tag) || isEvent) { // Grab the attributes while (*attr) @@ -371,49 +437,62 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) const char *key = *attr++; const char *value = *attr++; - if (strcmp("requestId", key) == 0) + if (!stricmp("requestId", key)) { - uuidString = value; + requestId = value; } - else if (strcmp("action", key) == 0) + else if (!stricmp("action", key)) { actionString = value; } - else if (strcmp("type", key) == 0) + else if (!stricmp("type", key)) { eventTypeString = value; } } } - LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")" << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; } else { if (ignoringTags) { - LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; } else { - LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")" << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; // Ignore the InputXml stuff so we don't get confused - if (strcmp("InputXml", tag) == 0) + if (!stricmp("InputXml", tag)) { ignoringTags = true; ignoreDepth = responseDepth; accumulateText = false; - LL_DEBUGS("Voice") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; } - else if (strcmp("CaptureDevices", tag) == 0) + else if (!stricmp("CaptureDevices", tag)) { gVoiceClient->clearCaptureDevices(); } - else if (strcmp("RenderDevices", tag) == 0) + else if (!stricmp("RenderDevices", tag)) { gVoiceClient->clearRenderDevices(); } + else if (!stricmp("Buddies", tag)) + { + gVoiceClient->deleteAllBuddies(); + } + else if (!stricmp("BlockRules", tag)) + { + gVoiceClient->deleteAllBlockRules(); + } + else if (!stricmp("AutoAcceptRules", tag)) + { + gVoiceClient->deleteAllAutoAcceptRules(); + } + } } responseDepth++; @@ -432,90 +511,138 @@ void LLVivoxProtocolParser::EndTag(const char *tag) { if (ignoreDepth == responseDepth) { - LL_DEBUGS("Voice") << "end of ignore" << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; ignoringTags = false; } else { - LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; } } if (!ignoringTags) { - LL_DEBUGS("Voice") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; // Closing a tag. Finalize the text we've accumulated and reset - if (strcmp("ReturnCode", tag) == 0) + if (!stricmp("ReturnCode", tag)) returnCode = strtol(string.c_str(), NULL, 10); - else if (strcmp("StatusCode", tag) == 0) + else if (!stricmp("SessionHandle", tag)) + sessionHandle = string; + else if (!stricmp("SessionGroupHandle", tag)) + sessionGroupHandle = string; + else if (!stricmp("StatusCode", tag)) statusCode = strtol(string.c_str(), NULL, 10); - else if (strcmp("ConnectorHandle", tag) == 0) + else if (!stricmp("StatusString", tag)) + statusString = string; + else if (!stricmp("ParticipantURI", tag)) + uriString = string; + else if (!stricmp("Volume", tag)) + volume = strtol(string.c_str(), NULL, 10); + else if (!stricmp("Energy", tag)) + energy = (F32)strtod(string.c_str(), NULL); + else if (!stricmp("IsModeratorMuted", tag)) + isModeratorMuted = !stricmp(string.c_str(), "true"); + else if (!stricmp("IsSpeaking", tag)) + isSpeaking = !stricmp(string.c_str(), "true"); + else if (!stricmp("Alias", tag)) + alias = string; + else if (!stricmp("NumberOfAliases", tag)) + numberOfAliases = strtol(string.c_str(), NULL, 10); + else if (!stricmp("Application", tag)) + applicationString = string; + else if (!stricmp("ConnectorHandle", tag)) connectorHandle = string; - else if (strcmp("AccountHandle", tag) == 0) + else if (!stricmp("VersionID", tag)) + versionID = string; + else if (!stricmp("AccountHandle", tag)) accountHandle = string; - else if (strcmp("SessionHandle", tag) == 0) - { - if (isEvent) - eventSessionHandle = string; - else - sessionHandle = string; - } - else if (strcmp("StatusString", tag) == 0) - statusString = string; - else if (strcmp("State", tag) == 0) + else if (!stricmp("State", tag)) state = strtol(string.c_str(), NULL, 10); - else if (strcmp("URI", tag) == 0) + else if (!stricmp("URI", tag)) uriString = string; - else if (strcmp("IsChannel", tag) == 0) - isChannel = string == "true" ? true : false; - else if (strcmp("Name", tag) == 0) + else if (!stricmp("IsChannel", tag)) + isChannel = !stricmp(string.c_str(), "true"); + else if (!stricmp("Incoming", tag)) + incoming = !stricmp(string.c_str(), "true"); + else if (!stricmp("Enabled", tag)) + enabled = !stricmp(string.c_str(), "true"); + else if (!stricmp("Name", tag)) nameString = string; - else if (strcmp("AudioMedia", tag) == 0) + else if (!stricmp("AudioMedia", tag)) audioMediaString = string; - else if (strcmp("ChannelName", tag) == 0) + else if (!stricmp("ChannelName", tag)) nameString = string; - else if (strcmp("ParticipantURI", tag) == 0) - uriString = string; - else if (strcmp("DisplayName", tag) == 0) + else if (!stricmp("DisplayName", tag)) displayNameString = string; - else if (strcmp("AccountName", tag) == 0) + else if (!stricmp("AccountName", tag)) nameString = string; - else if (strcmp("ParticipantTyppe", tag) == 0) + else if (!stricmp("ParticipantType", tag)) participantType = strtol(string.c_str(), NULL, 10); - else if (strcmp("IsLocallyMuted", tag) == 0) - isLocallyMuted = string == "true" ? true : false; - else if (strcmp("IsModeratorMuted", tag) == 0) - isModeratorMuted = string == "true" ? true : false; - else if (strcmp("IsSpeaking", tag) == 0) - isSpeaking = string == "true" ? true : false; - else if (strcmp("Volume", tag) == 0) - volume = strtol(string.c_str(), NULL, 10); - else if (strcmp("Energy", tag) == 0) - energy = (F32)strtod(string.c_str(), NULL); - else if (strcmp("MicEnergy", tag) == 0) + else if (!stricmp("IsLocallyMuted", tag)) + isLocallyMuted = !stricmp(string.c_str(), "true"); + else if (!stricmp("MicEnergy", tag)) energy = (F32)strtod(string.c_str(), NULL); - else if (strcmp("ChannelName", tag) == 0) + else if (!stricmp("ChannelName", tag)) nameString = string; - else if (strcmp("ChannelURI", tag) == 0) + else if (!stricmp("ChannelURI", tag)) uriString = string; - else if (strcmp("ChannelListResult", tag) == 0) - { - gVoiceClient->addChannelMapEntry(nameString, uriString); - } - else if (strcmp("Device", tag) == 0) + else if (!stricmp("BuddyURI", tag)) + uriString = string; + else if (!stricmp("Presence", tag)) + statusString = string; + else if (!stricmp("Device", tag)) { // This closing tag shouldn't clear the accumulated text. clearbuffer = false; } - else if (strcmp("CaptureDevice", tag) == 0) + else if (!stricmp("CaptureDevice", tag)) { gVoiceClient->addCaptureDevice(textBuffer); } - else if (strcmp("RenderDevice", tag) == 0) + else if (!stricmp("RenderDevice", tag)) { gVoiceClient->addRenderDevice(textBuffer); } + else if (!stricmp("Buddy", tag)) + { + gVoiceClient->processBuddyListEntry(uriString, displayNameString); + } + else if (!stricmp("BlockRule", tag)) + { + gVoiceClient->addBlockRule(blockMask, presenceOnly); + } + else if (!stricmp("BlockMask", tag)) + blockMask = string; + else if (!stricmp("PresenceOnly", tag)) + presenceOnly = string; + else if (!stricmp("AutoAcceptRule", tag)) + { + gVoiceClient->addAutoAcceptRule(autoAcceptMask, autoAddAsBuddy); + } + else if (!stricmp("AutoAcceptMask", tag)) + autoAcceptMask = string; + else if (!stricmp("AutoAddAsBuddy", tag)) + autoAddAsBuddy = string; + else if (!stricmp("MessageHeader", tag)) + messageHeader = string; + else if (!stricmp("MessageBody", tag)) + messageBody = string; + else if (!stricmp("NotificationType", tag)) + notificationType = string; + else if (!stricmp("HasText", tag)) + hasText = !stricmp(string.c_str(), "true"); + else if (!stricmp("HasAudio", tag)) + hasAudio = !stricmp(string.c_str(), "true"); + else if (!stricmp("HasVideo", tag)) + hasVideo = !stricmp(string.c_str(), "true"); + else if (!stricmp("Terminated", tag)) + terminated = !stricmp(string.c_str(), "true"); + else if (!stricmp("SubscriptionHandle", tag)) + subscriptionHandle = string; + else if (!stricmp("SubscriptionType", tag)) + subscriptionType = string; + if(clearbuffer) { @@ -550,144 +677,296 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length) void LLVivoxProtocolParser::processResponse(std::string tag) { - LL_DEBUGS("Voice") << tag << LL_ENDL; + LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; + // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. + // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", + // so I believe this will give correct behavior. + + if(returnCode == 0) + statusCode = 0; + if (isEvent) { - if (eventTypeString == "LoginStateChangeEvent") + const char *eventTypeCstr = eventTypeString.c_str(); + if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) { - gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state); + gVoiceClient->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); } - else if (eventTypeString == "SessionNewEvent") + else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) { - gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString); + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:confctl-1408789@bhr.vivox.com + true + false + + + */ + 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")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + 200 + OK + 2 + false + + */ + gVoiceClient->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); + } + else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==1 + true + 1 + true + + */ + gVoiceClient->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming); + } + else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 + sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com + xI5auBZ60SJWIk606-1JGRQ== + + 0 + + */ + gVoiceClient->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); + } + else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 + sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com + xtx7YNV-3SGiG7rA1fo5Ndw== + + */ + gVoiceClient->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); } - else if (eventTypeString == "ParticipantPropertiesEvent") + else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) { - gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com + false + true + 44 + 0.0879437 + + */ + + // These happen so often that logging them is pretty useless. + squelchDebugOutput = true; + + gVoiceClient->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); } - else if (eventTypeString == "AuxAudioPropertiesEvent") + 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")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw== + sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com + Monroe Tester + + 0 + Set + + */ + // 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")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:confctl-9@bhd.vivox.com + 0 + 50 + 1 + 0 + 000 + 0 + + */ + // 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 << "" << "V2 SDK" - << "" << mAccountServerURI << "" + << "" << mVoiceAccountServerURI << "" + << "Normal" << "" - << "false" << "" << logpath << "" << "Connector" << ".log" << "" << loglevel << "" << "" + << "SecondLifeViewer.1" << "\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,44 +2029,117 @@ void LLVoiceClient::stateMachine() sMuteListListener_listening = true; } - setState(stateNoChannel); - break; - - case stateNoChannel: - if(mSessionTerminateRequested || !mVoiceEnabled) - { - // MBW -- XXX -- Is this the right way out of this state? - setState(stateSessionTerminated); - } - else if(mTuningMode) + // Set up the friends list observer if it hasn't been set up already. + if(friendslist_listener == NULL) { - mTuningExitState = stateNoChannel; - setState(stateMicTuningStart); + friendslist_listener = new LLVoiceClientFriendsObserver; + LLAvatarTracker::instance().addObserver(friendslist_listener); } - else if(!mNextSessionHandle.empty()) + + // Set the initial state of mic mute, local speaker volume, etc. { - setState(stateSessionConnect); + std::ostringstream stream; + + buildLocalAudioUpdates(stream); + + if(!stream.str().empty()) + { + writeString(stream.str()); + } } - else if(!mNextSessionURI.empty()) + +#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) { - setState(stateSessionCreate); + // TODO: Question: is this the right way out of this state + setState(stateSessionTerminated); } - break; + else if(!mMainSessionGroupHandle.empty()) + { + setState(stateNoChannel); + + // Start looped recording (needed for "panic button" anti-griefing tool) + recordingLoopStart(); - case stateSessionCreate: - sessionCreateSendMessage(); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - setState(stateJoiningSession); + // Initial kick-off of channel lookup logic + parcelChanged(); + } break; - - case stateSessionConnect: - sessionConnectSendMessage(); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - setState(stateJoiningSession); + + //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) + { + // TODO: Question: Is this the right way out of this state? + setState(stateSessionTerminated); + } + else if(mTuningMode) + { + mTuningExitState = stateNoChannel; + setState(stateMicTuningStart); + } + else if(sessionNeedsRelog(mNextAudioSession)) + { + requestRelog(); + setState(stateSessionTerminated); + } + 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()) + { + // If we're not headed elsewhere and have a spatial URI, return to spatial. + switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); + } 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 << "" << "" << mConnectorHandle << "" << "" << mAccountName << "" << "" << mAccountPassword << "" << "VerifyAnswer" + << "true" + << "Application" + << "5" + << (autoPostCrashDumps?"true":"") << "\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 - << "" - << "" << mAccountHandle << "" - << "\n\n\n"; + if(!mAccountHandle.empty()) + { + std::ostringstream stream; - writeString(stream.str()); + LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL; + + stream + << "" + << "" << mAccountHandle << "" + << "" + << "\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 + << "" + << "" << mAccountHandle << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVoiceClient::sessionGroupCreateSendMessage() +{ + if(!mAccountHandle.empty()) + { + std::ostringstream stream; - mSessionURI = mNextSessionURI; - mNonSpatialChannel = !mNextSessionSpatial; - mSessionResetOnClose = mNextSessionResetOnClose; - mNextSessionResetOnClose = false; - if(mNextSessionNoReconnect) + LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; + + stream + << "" + << "" << mAccountHandle << "" + << "Normal" + << "" + << "\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 - << "" + << "mSIPURI << "\" action=\"Session.Create.1\">" << "" << mAccountHandle << "" - << "" << mSessionURI << ""; + << "" << session->mSIPURI << ""; static const std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "0123456789" "-._~"; - if(!mNextSessionHash.empty()) + if(!session->mHash.empty()) { stream - << "" << LLURI::escape(mNextSessionHash, allowed_chars) << "" + << "" << LLURI::escape(session->mHash, allowed_chars) << "" << "SHA1UserName"; } stream + << "" << (startAudio?"true":"false") << "" + << "" << (startText?"true":"false") << "" << "" << mChannelName << "" << "\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; + } + + 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 + << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mSIPURI << "" + << "" << mChannelName << "" + << "" << (startAudio?"true":"false") << "" + << "" << (startText?"true":"false") << "" + << "" << password << "" + << "SHA1UserName" + << "\n\n\n" + ; - 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; + 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 + << "mHandle << "\" action=\"Session.MediaConnect.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "Audio" + << "\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 - << "" - << "" << mSessionHandle << "" - << "default" + << "mHandle << "\" action=\"Session.TextConnect.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" << "\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 << "" - << "" << sessionHandle << "" - << "" - << "\n\n\n"; + << "" << session->mHandle << "" + << "\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 + << "" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "Audio" + << "\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 + << "" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "\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,47 +2950,31 @@ 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); } -void LLVoiceClient::sendPositionalUpdate(void) -{ - std::ostringstream stream; +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]; - if(mSpatialCoordsDirty) - { - LLVector3 l, u, a; - - // Always send both speaker and listener positions together. - stream << "" - << "" << mSessionHandle << ""; - - stream << ""; - - l = mAvatarRot.getLeftRow(); - u = mAvatarRot.getUpRow(); - a = mAvatarRot.getFwdRow(); - - LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; - - stream + // The original XML command was sent like this: + /* << "" - << "" << mAvatarPosition[VX] << "" - << "" << mAvatarPosition[VZ] << "" - << "" << mAvatarPosition[VY] << "" + << "" << pos[VX] << "" + << "" << pos[VZ] << "" + << "" << pos[VY] << "" << "" << "" << "" << mAvatarVelocity[VX] << "" @@ -2396,6 +2996,148 @@ void LLVoiceClient::sendPositionalUpdate(void) << "" << u.mV [VZ] << "" << "" << a.mV [VY] << "" << ""; + */ + +#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, vel; + LLVector3d pos; + + mSpatialCoordsDirty = false; + + // Always send both speaker and listener positions together. + stream << "" + << "" << getAudioSessionHandle() << ""; + + stream << ""; + +// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; + l = mAvatarRot.getLeftRow(); + u = mAvatarRot.getUpRow(); + a = mAvatarRot.getFwdRow(); + pos = mAvatarPosition; + vel = mAvatarVelocity; + + // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. + // The old transform is replicated by this function. + oldSDKTransform(l, u, a, pos, vel); + + stream + << "" + << "" << pos.mdV[VX] << "" + << "" << pos.mdV[VY] << "" + << "" << pos.mdV[VZ] << "" + << "" + << "" + << "" << vel.mV[VX] << "" + << "" << vel.mV[VY] << "" + << "" << vel.mV[VZ] << "" + << "" + << "" + << "" << a.mV[VX] << "" + << "" << a.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << u.mV[VX] << "" + << "" << u.mV[VY] << "" + << "" << u.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VX] << "" + << "" << l.mV [VY] << "" + << "" << l.mV [VZ] << "" + << ""; stream << ""; @@ -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 << "" - << "" << earPosition[VX] << "" - << "" << earPosition[VZ] << "" - << "" << earPosition[VY] << "" + << "" << pos.mdV[VX] << "" + << "" << pos.mdV[VY] << "" + << "" << pos.mdV[VZ] << "" << "" << "" - << "" << earVelocity[VX] << "" - << "" << earVelocity[VZ] << "" - << "" << earVelocity[VY] << "" + << "" << vel.mV[VX] << "" + << "" << vel.mV[VY] << "" + << "" << vel.mV[VZ] << "" << "" << "" - << "" << l.mV[VX] << "" - << "" << u.mV[VX] << "" - << "" << a.mV[VX] << "" + << "" << a.mV[VX] << "" + << "" << a.mV[VY] << "" + << "" << a.mV[VZ] << "" << "" << "" - << "" << l.mV[VZ] << "" + << "" << u.mV[VX] << "" << "" << u.mV[VY] << "" - << "" << a.mV[VZ] << "" + << "" << u.mV[VZ] << "" << "" << "" - << "" << l.mV [VY] << "" - << "" << u.mV [VZ] << "" - << "" << a.mV [VY] << "" + << "" << l.mV [VX] << "" + << "" << l.mV [VY] << "" + << "" << l.mV [VZ] << "" << ""; + stream << ""; stream << "\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 << "" + << "" << getAudioSessionHandle() << "" + << "" << p->mURI << "" + << "" << volume << "" + << "\n\n\n"; + + // Send a "mute for me" command for the user + stream << "" + << "" << getAudioSessionHandle() << "" + << "" << p->mURI << "" + << "" << (mute?"1":"0") << "" + << "\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 + << "" + << "" << mCaptureDevice << "" + << "" + << "\n\n\n"; + + mCaptureDeviceDirty = false; + } +} + +void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream) +{ + if(mRenderDeviceDirty) + { + LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; + + stream + << "" + << "" << mRenderDevice << "" + << "" + << "\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,286 +3334,476 @@ void LLVoiceClient::sendPositionalUpdate(void) << "" << mConnectorHandle << "" << "" << (mPTT?"false":"true") << "" << "\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 << "" - << "" << mSessionHandle << "" - << "" << p->mURI << "" - << "" << volume << "" - << "\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 << "" << "" << mConnectorHandle << "" << "" << muteval << "" - << "\n\n\n"; + << "\n\n\n"; + } if(mSpeakerVolumeDirty) { + mSpeakerVolumeDirty = false; + LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; stream << "" << "" << mConnectorHandle << "" << "" << mSpeakerVolume << "" - << "\n\n\n"; + << "\n\n\n"; + } if(mMicVolumeDirty) { + mMicVolumeDirty = false; + LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; stream << "" << "" << mConnectorHandle << "" << "" << mMicVolume << "" - << "\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) - { - buildSetRenderDevice(stream); - } - - mSpatialCoordsDirty = false; - mPTTDirty = false; - mVolumeDirty = false; - mSpeakerVolumeDirty = false; - mMicVolumeDirty = false; - mSpeakerMuteDirty = false; - mCaptureDeviceDirty = false; - mRenderDeviceDirty = false; - - if(!stream.str().empty()) - { - writeString(stream.str()); + << "\n\n\n"; } -} -void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) -{ - LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - stream - << "" - << "" << mCaptureDevice << "" - << "" - << "\n\n\n"; } -void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream) +void LLVoiceClient::checkFriend(const LLUUID& id) { - LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; + std::string name; + buddyListEntry *buddy = findBuddy(id); - stream - << "" - << "" << mRenderDevice << "" - << "" - << "\n\n\n"; -} + // Make sure we don't add a name before it's been looked up. + if(gCacheName->getFullName(id, name)) + { -///////////////////////////// -// Response/Event handlers + 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. -void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL; - setState(stateConnectorFailed); + 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; + } else { - // Connector created, move forward. - mConnectorHandle = connectorHandle; - if(getState() == stateConnectorStarting) + // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has. + if(buddy) { - setState(stateConnectorStarted); + 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::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle) -{ - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - - // Status code of 20200 means "bad password". We may want to special-case that at some point. +void LLVoiceClient::clearAllLists() +{ + // FOR TESTING ONLY - if ( statusCode == 401 ) - { - // Login failure which is probably caused by the delay after a user's password being updated. - LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - setState(stateLoginRetry); - } - else if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - setState(stateLoginFailed); - } - else + // 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();) { - // Login succeeded, move forward. - mAccountHandle = accountHandle; - // MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received. -// if(getState() == stateLoggingIn) -// { -// setState(stateLoggedIn); -// } - } -} + buddyListEntry *buddy = buddy_it->second; + buddy_it++; + + std::ostringstream stream; -void LLVoiceClient::channelGetListResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.ChannelGetList response failure: " << statusString << LL_ENDL; - switchChannel(); - } - 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; + 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 << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; } - else + + if(buddy->mHasBlockListEntry) { - // We have a sip URL for this area. - LL_INFOS("Voice") << "mapped channel " << mChannelName << " to URI "<< uri << LL_ENDL; + // Delete the associated block list entry (so the block list doesn't fill up with junk) + buddy->mHasBlockListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; } - - // switchChannel with an empty uri string will do the right thing (leave channel and not rejoin) - switchChannel(uri); + 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 << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + + writeString(stream.str()); + } } -void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle) -{ - if(statusCode != 0) +void LLVoiceClient::sendFriendsListUpdates() +{ + if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty) { - 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 + mFriendsListDirty = false; + + if(0) { - mVivoxErrorStatusCode = statusCode; - mVivoxErrorStatusString = statusString; - setState(stateJoinSessionFailed); + // FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries. + clearAllLists(); + return; } - } - else - { - LL_DEBUGS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; - if(getState() == stateJoiningSession) + + 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++) { - // This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early... - mSessionHandle = sessionHandle; + // reset the temp flags in the local buddy list + buddy_it->second->mInSLFriends = false; } - else + + // 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; + + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) { - // We should never get a session.create response in any state except stateJoiningSession. Things are out of sync. Kill this session. - sessionTerminateByHandle(sessionHandle); + 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 + << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "" << buddy->mDisplayName << "" + << "" // Without this, SLVoice doesn't seem to parse the command. + << "0" + << "\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 << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + + if(buddy->mHasBlockListEntry) + { + // Delete the associated block list entry, if any + buddy->mHasBlockListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + if(buddy->mHasAutoAcceptListEntry) + { + // Delete the associated auto-accept list entry, if any + buddy->mHasAutoAcceptListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\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 << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + + + // If we just deleted a block list entry, add an auto-accept entry. + if(!buddy->mHasAutoAcceptListEntry) + { + buddy->mHasAutoAcceptListEntry = true; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "0" + << "\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 << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + + // If we just deleted an auto-accept entry, add a block list entry. + if(!buddy->mHasBlockListEntry) + { + buddy->mHasBlockListEntry = true; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "1" + << "\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()); + } } } } -void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString) -{ +///////////////////////////// +// Response/Event handlers + +void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) +{ if(statusCode != 0) { - LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; -// if(statusCode == 1015) + LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL; + setState(stateConnectorFailed); + } + else + { + // Connector created, move forward. + LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL; + mConnectorHandle = connectorHandle; + if(getState() == stateConnectorStarting) + { + setState(stateConnectorStarted); + } + } +} + +void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) +{ + LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; + + // Status code of 20200 means "bad password". We may want to special-case that at some point. + + if ( statusCode == 401 ) + { + // Login failure which is probably caused by the delay after a user's password being updated. + LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; + setState(stateLoginRetry); + } + else if(statusCode != 0) + { + LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; + setState(stateLoginFailed); + } + else + { + // Login succeeded, move forward. + mAccountHandle = accountHandle; + mNumberOfAliases = numberOfAliases; + // This needs to wait until the AccountLoginStateChangeEvent is received. +// if(getState() == stateLoggingIn) // { -// LL_WARNS("Voice") << "terminating existing session" << LL_ENDL; -// sessionTerminate(); +// setState(stateLoggedIn); // } -// else + } +} + +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") << "Session.Create 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.Connect response received (success)" << LL_ENDL; + LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; + if(session) + { + setSessionHandle(session, sessionHandle); + } } } -void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString) +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") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) + { + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + { + setState(stateJoinSessionFailed); + } + else + { + reapSession(session); + } + } + } + else + { + LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; + if(session) + { + setSessionHandle(session, sessionHandle); + } + } +} + +void LLVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) +{ + sessionState *session = findSession(requestId); if(statusCode != 0) { - LL_WARNS("Voice") << "Session.Terminate response failure: (" << statusCode << "): " << statusString << LL_ENDL; - if(getState() == stateLeavingSession) + LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) { - // This is probably "(404): Server reporting Failure. Not a member of this conference." - // Do this so we don't get stuck. - setState(stateSessionTerminated); + session->mMediaConnectInProgress = false; + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + setState(stateJoinSessionFailed); } } - + else + { + LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; + } } void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString) @@ -2764,7 +3811,7 @@ 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,448 +3836,1124 @@ 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? - } - - break; - case 5: // I see this when leaving the session - LL_INFOS("Voice") << "left session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL; - - // 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) - { - // 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) - { - mSessionResetOnClose = false; - mNonSpatialChannel = false; - mNextSessionSpatial = true; - parcelChanged(); - } - - removeAllParticipants(); + 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; - switch(getState()) + // 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()) { - 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; - - 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; - - default: - LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL; - setState(stateSessionTerminated); - break; + // Didn't seem to be a SIP URI, just use the whole provided name. + namePortion = nameString; } - - // store status values for later notification of observers - mVivoxErrorStatusCode = statusCode; - mVivoxErrorStatusString = statusString; + + // 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); } - else + + LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; + + if(!session->mSynthesizedCallerID) { - LL_INFOS("Voice") << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL; + // If we got here, we don't have a proper name. Initiate a lookup. + lookupName(session->mCallerID); } - - mSessionStateEventHandle.clear(); - mSessionStateEventURI.clear(); - break; - default: - LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL; - break; + } } } -void LLVoiceClient::loginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, - int state) +void LLVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) { - LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; - /* - According to Mike S., status codes for this event are: - login_state_logged_out=0, - login_state_logged_in = 1, - login_state_logging_in = 2, - login_state_logging_out = 3, - login_state_resetting = 4, - login_state_error=100 - */ + LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - switch(state) +#if USE_SESSION_GROUPS + if(mMainSessionGroupHandle.empty()) { - case 1: - if(getState() == stateLoggingIn) - { - setState(stateLoggedIn); - } - break; - - default: - //Used to be a commented out warning - LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; - break; + // 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::sessionNewEvent( - std::string &accountHandle, - std::string &eventSessionHandle, - int state, - std::string &nameString, - std::string &uriString) +void LLVoiceClient::joinedAudioSession(sessionState *session) { - LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; + if(mAudioSession != session) + { + sessionState *oldSession = mAudioSession; + + mAudioSession = session; + mAudioSessionChanged = true; + + // The old session may now need to be deleted. + reapSession(oldSession); + } - switch(state) + // This is the session we're joining. + if(getState() == stateJoiningSession) { - case 0: - { - LL_DEBUGS("Voice") << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << LL_ENDL; + 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); - LLUUID caller_id; - if(IDFromName(nameString, caller_id)) + 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) + { + if(participant->mAvatarIDValid) { - 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(participant->mAvatarID); } - else + else if(!session->mName.empty()) { - LL_WARNS("Voice") << "Could not generate caller id from uri " << uriString << LL_ENDL; + participant->mDisplayName = session->mName; + avatarNameResolved(participant->mAvatarID, session->mName); } + + // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? + LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; } - break; - - default: - LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL; - break; + } } } -void LLVoiceClient::participantStateChangeEvent( - std::string &uriString, - int statusCode, - std::string &statusString, - int state, - std::string &nameString, - std::string &displayNameString, - int participantType) +void LLVoiceClient::sessionRemovedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle) { - participantState *participant = NULL; - LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; + LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; + + sessionState *session = findSession(sessionHandle); + if(session) + { + leftAudioSession(session); - switch(state) + // 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 { - case 7: // I see this when a participant joins - participant = addParticipant(uriString); - if(participant) - { - participant->mName = nameString; - LL_DEBUGS("Voice") << "added participant \"" << participant->mName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - break; - case 9: // I see this when a participant leaves - participant = findParticipant(uriString); - if(participant) - { - removeParticipant(participant); - } - break; - default: - LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; - break; + LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; } } -void LLVoiceClient::participantPropertiesEvent( - std::string &uriString, - int statusCode, - std::string &statusString, - bool isLocallyMuted, - bool isModeratorMuted, - bool isSpeaking, - int volume, - F32 energy) +void LLVoiceClient::reapSession(sessionState *session) { - participantState *participant = findParticipant(uriString); - if(participant) + if(session) { - participant->mPTT = !isLocallyMuted; - participant->mIsSpeaking = isSpeaking; - participant->mIsModeratorMuted = isModeratorMuted; - if (isSpeaking) + 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) { - participant->mSpeakingTimeout.reset(); + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; } - participant->mPower = energy; - participant->mVolume = volume; + 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_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; +// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; } } -void LLVoiceClient::auxAudioPropertiesEvent(F32 energy) -{ - LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL; - mTuningEnergy = energy; -} - -void LLVoiceClient::muteListChanged() +// Returns true if the session seems to indicate we've moved to a region on a different voice server +bool LLVoiceClient::sessionNeedsRelog(sessionState *session) { - // 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(); + bool result = false; - for(; iter != mParticipantMap.end(); iter++) + if(session != NULL) { - participantState *p = iter->second; - - // Check to see if this participant is on the mute list already - updateMuteState(p); + // Only make this check for spatial channels (so it won't happen for group or p2p calls) + if(session->mIsSpatial) + { + std::string::size_type atsign; + + atsign = session->mSIPURI.find("@"); + + if(atsign != std::string::npos) + { + std::string urihost = session->mSIPURI.substr(atsign + 1); + if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) + { + // The hostname in this URI is different from what we expect. This probably means we need to relog. + + // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of + // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. + + result = true; + } + } + } } + + return result; } -///////////////////////////// -// 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) +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; + } + } } -LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri) +void LLVoiceClient::accountLoginStateChangeEvent( + std::string &accountHandle, + int statusCode, + std::string &statusString, + int state) { - participantState *result = NULL; - - participantMap::iterator iter = mParticipantMap.find(uri); + LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; + /* + According to Mike S., status codes for this event are: + login_state_logged_out=0, + login_state_logged_in = 1, + login_state_logging_in = 2, + login_state_logging_out = 3, + login_state_resetting = 4, + login_state_error=100 + */ - if(iter != mParticipantMap.end()) + switch(state) { - // Found a matching participant already in the map. - result = iter->second; + case 1: + if(getState() == stateLoggingIn) + { + setState(stateLoggedIn); + } + break; + + default: + //Used to be a commented out warning + LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; + break; } +} - if(!result) +void LLVoiceClient::mediaStreamUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + int statusCode, + std::string &statusString, + int state, + bool incoming) +{ + sessionState *session = findSession(sessionHandle); + + LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; + + if(session) { - // participant isn't already in one list or the other. - result = new participantState(uri); - mParticipantMap.insert(participantMap::value_type(uri, result)); - mParticipantMapChanged = true; + // We know about this session - // Try to do a reverse transform on the URI to get the GUID back. + // Save the state for later use + session->mMediaStreamState = state; + + switch(statusCode) { - LLUUID id; - if(IDFromName(uri, id)) - { - result->mAvatarIDValid = true; - result->mAvatarID = id; + 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; - updateMuteState(result); - } + 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; + } - LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; } - - return result; + else + { + LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL; + } } -void LLVoiceClient::updateMuteState(participantState *p) +void LLVoiceClient::textStreamUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + bool enabled, + int state, + bool incoming) { - if(p->mAvatarIDValid) + sessionState *session = findSession(sessionHandle); + + if(session) { - bool isMuted = LLMuteList::getInstance()->isMuted(p->mAvatarID, LLMute::flagVoiceChat); - if(p->mOnMuteList != isMuted) + // Save the state for later use + session->mTextStreamState = state; + + // We know about this session + switch(state) { - p->mOnMuteList = isMuted; - p->mVolumeDirty = true; - mVolumeDirty = true; + 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()) + { + 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; + } } } -void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant) +void LLVoiceClient::participantAddedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString, + std::string &displayNameString, + int participantType) { - if(participant) + sessionState *session = findSession(sessionHandle); + if(session) { - participantMap::iterator iter = mParticipantMap.find(participant->mURI); - - LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; + participantState *participant = session->addParticipant(uriString); + if(participant) + { + participant->mAccountName = nameString; + + LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - mParticipantMap.erase(iter); - delete participant; - mParticipantMapChanged = true; + if(participant->mAvatarIDValid) + { + // Initiate a lookup + lookupName(participant->mAvatarID); + } + else + { + // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. + std::string namePortion = nameFromsipURI(uriString); + if(namePortion.empty()) + { + // Problem with the SIP URI, fall back to the display name + namePortion = displayNameString; + } + if(namePortion.empty()) + { + // Problems with both of the above, fall back to the account name + namePortion = nameString; + } + + // Set the display name (which is a hint to the active speakers window not to do its own lookup) + participant->mDisplayName = namePortion; + avatarNameResolved(participant->mAvatarID, namePortion); + } + } } } -void LLVoiceClient::removeAllParticipants() +void LLVoiceClient::participantRemovedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString) { - LL_DEBUGS("Voice") << "called" << LL_ENDL; - - while(!mParticipantMap.empty()) + 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 { - removeParticipant(mParticipantMap.begin()->second); + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; } } -LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void) -{ - return &mParticipantMap; -} - -LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri) +void LLVoiceClient::participantUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + bool isModeratorMuted, + bool isSpeaking, + int volume, + F32 energy) { - 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) + sessionState *session = findSession(sessionHandle); + if(session) { - participantMap::iterator iter = mParticipantMap.find(uri); - - if(iter != mParticipantMap.end()) + participantState *participant = session->findParticipant(uriString); + + if(participant) { - result = iter->second; + 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; } } - - return result; + else + { + LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL; + } } - -LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar) +void LLVoiceClient::buddyPresenceEvent( + std::string &uriString, + std::string &alias, + std::string &statusString, + std::string &applicationString) { - participantState * result = NULL; - - // You'd think this would work, but it doesn't... -// std::string uri = sipURIFromAvatar(avatar); + buddyListEntry *buddy = findBuddy(uriString); - // Currently, the URI is just the account name. - std::string loginName = nameFromAvatar(avatar); - result = findParticipant(loginName); - - if(result != NULL) + if(buddy) { - if(!result->mAvatarIDValid) + 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()) { - 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); + // 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(); } - - return result; + else + { + LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL; + } } -LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id) +void LLVoiceClient::messageEvent( + std::string &sessionHandle, + std::string &uriString, + std::string &alias, + std::string &messageHeader, + std::string &messageBody, + std::string &applicationString) { - participantState * result = NULL; + 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; - // Currently, the URI is just the account name. - std::string loginName = nameFromID(id); - result = findParticipant(loginName); + { + const std::string startMarker = ", try looking for a 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 + << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "" + << ""<< (buddy->mInSLFriends?"1":"0")<< "" + << "" << subscriptionHandle << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} -void LLVoiceClient::clearChannelMap(void) +void LLVoiceClient::auxAudioPropertiesEvent(F32 energy) { - mChannelMap.clear(); + LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL; + mTuningEnergy = energy; } -void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri) +void LLVoiceClient::buddyListChanged() { - LL_DEBUGS("Voice") << "Adding channel name mapping: " << name << " -> " << uri << LL_ENDL; - mChannelMap.insert(channelMap::value_type(name, uri)); + // This is called after we receive a BuddyAndGroupListChangedEvent. + mBuddyListMapPopulated = true; + mFriendsListDirty = true; } -std::string LLVoiceClient::findChannelURI(std::string &name) +void LLVoiceClient::muteListChanged() { - std::string result; + // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. + if(mAudioSession) + { + participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); + + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) + { + 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), + mOnMuteList(false), + mUserVolume(100), + mVolumeDirty(false), + mAvatarIDValid(false), + mIsSelf(false) +{ +} + +LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(const std::string &uri) +{ + participantState *result = NULL; + bool useAlternateURI = false; + + // 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. + { + 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(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(result->mURI, id)) + { + result->mAvatarIDValid = true; + result->mAvatarID = id; + + 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; +} + +bool LLVoiceClient::participantState::updateMuteState() +{ + bool result = false; + + if(mAvatarIDValid) + { + bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); + if(mOnMuteList != isMuted) + { + mOnMuteList = isMuted; + mVolumeDirty = true; + result = true; + } + } + return result; +} + +void LLVoiceClient::sessionState::removeParticipant(LLVoiceClient::participantState *participant) +{ + if(participant) + { + participantMap::iterator iter = mParticipantsByURI.find(&(participant->mURI)); + participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(&(participant->mAvatarID)); + + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; + + if(iter == mParticipantsByURI.end()) + { + LL_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::sessionState::removeAllParticipants() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while(!mParticipantsByURI.empty()) + { + 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) +{ + participantMap *result = NULL; + if(mAudioSession) + { + result = &(mAudioSession->mParticipantsByURI); + } + return result; +} + + +LLVoiceClient::participantState *LLVoiceClient::sessionState::findParticipant(const std::string &uri) +{ + participantState *result = NULL; - channelMap::iterator iter = mChannelMap.find(name); + participantMap::iterator iter = mParticipantsByURI.find(&uri); + + if(iter == mParticipantsByURI.end()) + { + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) + { + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Look up the other URI + iter = mParticipantsByURI.find(&(mSIPURI)); + } + } + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + + return result; +} + +LLVoiceClient::participantState* LLVoiceClient::sessionState::findParticipantByID(const LLUUID& id) +{ + participantState * result = NULL; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(&id); - if(iter != mChannelMap.end()) + if(iter != mParticipantsByUUID.end()) { result = iter->second; } + + return result; +} + +LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id) +{ + participantState * result = NULL; + + if(mAudioSession) + { + 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; - } - break; - } - + 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,808 +5076,1710 @@ void LLVoiceClient::switchChannel( } } -void LLVoiceClient::joinSession(std::string handle, std::string uri) +void LLVoiceClient::joinSession(sessionState *session) +{ + mNextAudioSession = session; + + if(getState() <= stateNoChannel) + { + // We're already set up to join a channel, just needed to fill in the session handle + } + else + { + // State machine will come around and rejoin if uri/handle is not empty. + sessionTerminate(); + } +} + +void LLVoiceClient::setNonSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + switchChannel(uri, false, false, false, credentials); +} + +void LLVoiceClient::setSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + mSpatialSessionURI = uri; + mSpatialSessionCredentials = credentials; + mAreaVoiceDisabled = mSpatialSessionURI.empty(); + + LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; + + if((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, false, mSpatialSessionCredentials); + } +} + +void LLVoiceClient::callUser(const LLUUID &uuid) +{ + std::string userURI = sipURIFromID(uuid); + + 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; +} + +bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) +{ + 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 + << "" + << "" << session->mHandle << "" + << "text/HTML" + << "" << message << "" + << "" + << "\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) +{ + sessionState *session = findSession(sessionHandle); + if(session) + { + sessionMediaDisconnectSendMessage(session); + } +} + +void LLVoiceClient::leaveNonSpatialChannel() +{ + 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) + { + result = getAudioSessionURI(); + } + + return result; +} + +bool LLVoiceClient::inProximalChannel() +{ + bool result = false; + + if((getState() == stateRunning) && !mSessionTerminateRequested) + { + result = inSpatialChannel(); + } + + return result; +} + +std::string LLVoiceClient::sipURIFromID(const LLUUID &id) +{ + std::string result; + result = "sip:"; + result += nameFromID(id); + result += "@"; + result += mVoiceSIPURIHostName; + + return result; +} + +std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = "sip:"; + result += nameFromID(avatar->getID()); + result += "@"; + result += mVoiceSIPURIHostName; + } + + return result; +} + +std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = nameFromID(avatar->getID()); + } + return result; +} + +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"; + + // Base64 encode and replace the pieces of base64 that are less compatible + // with e-mail local-parts. + // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" + result += LLBase64::encode(uuid.mData, UUID_BYTES); + LLStringUtil::replaceChar(result, '+', '-'); + LLStringUtil::replaceChar(result, '/', '_'); + + // If you need to transform a GUID to this form on the 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 inName, LLUUID &uuid) +{ + bool result = false; + + // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" + // If it is, convert to a bare name before doing the transform. + std::string name = nameFromsipURI(inName); + + // Doesn't look like a SIP URI, assume it's an actual name. + if(name.empty()) + name = inName; + + // This will only work if the name is of the proper form. + // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: + // "xFnPP04IpREWNkuw1cOXlhw==" + + if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) + { + // The name appears to have the right form. + + // Reverse the transforms done by nameFromID + std::string temp = name; + LLStringUtil::replaceChar(temp, '-', '+'); + LLStringUtil::replaceChar(temp, '_', '/'); + + U8 rawuuid[UUID_BYTES + 1]; + int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); + if(len == UUID_BYTES) + { + // The decode succeeded. Stuff the bits into the result's UUID + memcpy(uuid.mData, rawuuid, UUID_BYTES); + result = true; + } + } + + if(!result) + { + // VIVOX: not a standard account name, just copy the URI name mURIString field + // and hope for the best. bpj + uuid.setNull(); // VIVOX, set the uuid field to nulls + } + + return result; +} + +std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) +{ + return avatar->getFullname(); +} + +std::string LLVoiceClient::sipURIFromName(std::string &name) +{ + std::string result; + result = "sip:"; + result += name; + result += "@"; + 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 + +void LLVoiceClient::enforceTether(void) +{ + LLVector3d tethered = mCameraRequestedPosition; + + // constrain 'tethered' to within 50m of mAvatarPosition. + { + F32 max_dist = 50.0f; + LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; + F32 camera_distance = (F32)camera_offset.magVec(); + if(camera_distance > max_dist) + { + tethered = mAvatarPosition + + (max_dist / camera_distance) * camera_offset; + } + } + + if(dist_vec(mCameraPosition, tethered) > 0.1) + { + mCameraPosition = tethered; + mSpatialCoordsDirty = true; + } +} + +void LLVoiceClient::updatePosition(void) +{ + + if(gVoiceClient) + { + LLVOAvatar *agent = gAgent.getAvatarObject(); + LLViewerRegion *region = gAgent.getRegion(); + if(region && agent) + { + LLMatrix3 rot; + LLVector3d pos; + + // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... + // They're currently always set to zero. + + // Send the current camera position to the voice code + rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); + pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); + + gVoiceClient->setCameraPosition( + pos, // position + LLVector3::zero, // velocity + rot); // rotation matrix + + // Send the current avatar position to the voice code + rot = agent->getRootJoint()->getWorldRotation().getMatrix3(); + + pos = agent->getPositionGlobal(); + // TODO: Can we get the head offset from outside the LLVOAvatar? +// pos += LLVector3d(mHeadOffset); + pos += LLVector3d(0.f, 0.f, 1.f); + + gVoiceClient->setAvatarPosition( + pos, // position + LLVector3::zero, // velocity + rot); // rotation matrix + } + } +} + +void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + mCameraRequestedPosition = position; + + if(mCameraVelocity != velocity) + { + mCameraVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mCameraRot != rot) + { + mCameraRot = rot; + mSpatialCoordsDirty = true; + } +} + +void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + if(dist_vec(mAvatarPosition, position) > 0.1) + { + mAvatarPosition = position; + mSpatialCoordsDirty = true; + } + + if(mAvatarVelocity != velocity) + { + mAvatarVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mAvatarRot != rot) + { + mAvatarRot = rot; + mSpatialCoordsDirty = true; + } +} + +bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) +{ + bool result = false; + + if(region) + { + name = region->getName(); + } + + if(!name.empty()) + result = true; + + return result; +} + +void LLVoiceClient::leaveChannel(void) +{ + if(getState() == stateRunning) + { + LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; + mChannelName.clear(); + sessionTerminate(); + } +} + +void LLVoiceClient::setMuteMic(bool muted) +{ + mMuteMic = muted; +} + +void LLVoiceClient::setUserPTTState(bool ptt) +{ + mUserPTTState = ptt; +} + +bool LLVoiceClient::getUserPTTState() +{ + return mUserPTTState; +} + +void LLVoiceClient::toggleUserPTTState(void) +{ + mUserPTTState = !mUserPTTState; +} + +void LLVoiceClient::setVoiceEnabled(bool enabled) +{ + if (enabled != mVoiceEnabled) + { + mVoiceEnabled = enabled; + if (enabled) + { + LLVoiceChannel::getCurrentVoiceChannel()->activate(); + } + else + { + // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. + LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); + } + } +} + +bool LLVoiceClient::voiceEnabled() +{ + return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice"); +} + +void LLVoiceClient::setLipSyncEnabled(BOOL enabled) +{ + mLipSyncEnabled = enabled; +} + +BOOL LLVoiceClient::lipSyncEnabled() { - mNextSessionURI.clear(); - mNextSessionHash.clear(); - mNextP2PSessionURI = uri; - mNextSessionHandle = handle; - mNextSessionSpatial = false; - mNextSessionNoReconnect = false; - - if(getState() <= stateNoChannel) + + if ( mVoiceEnabled && stateDisabled != getState() ) { - // We're already set up to join a channel, just needed to fill in the session handle + return mLipSyncEnabled; } else { - // State machine will come around and rejoin if uri/handle is not empty. - sessionTerminate(); + return FALSE; } } -void LLVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) +void LLVoiceClient::setUsePTT(bool usePTT) { - switchChannel(uri, false, false, credentials); + if(usePTT && !mUsePTT) + { + // When the user turns on PTT, reset the current state. + mUserPTTState = false; + } + mUsePTT = usePTT; } -void LLVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) +void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) { - mSpatialSessionURI = uri; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); - - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; + if(!PTTIsToggle && mPTTIsToggle) + { + // When the user turns off toggle, reset the current state. + mUserPTTState = false; + } - if(mNonSpatialChannel || !mNextSessionSpatial) + mPTTIsToggle = PTTIsToggle; +} + + +void LLVoiceClient::setPTTKey(std::string &key) +{ + if(key == "MiddleMouse") { - // 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; + mPTTIsMiddleMouse = true; } else { - switchChannel(mSpatialSessionURI, true, false, credentials); + mPTTIsMiddleMouse = false; + if(!LLKeyboard::keyFromString(key, &mPTTKey)) + { + // If the call failed, don't match any key. + key = KEY_NONE; + } } } -void LLVoiceClient::callUser(LLUUID &uuid) +void LLVoiceClient::setEarLocation(S32 loc) { - std::string userURI = sipURIFromID(uuid); - - switchChannel(userURI, false, true); + if(mEarLocation != loc) + { + LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; + + mEarLocation = loc; + mSpatialCoordsDirty = true; + } } -void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id) +void LLVoiceClient::setVoiceVolume(F32 volume) { - joinSession(sessionHandle, sipURIFromID(other_user_id)); + int scaled_volume = scale_speaker_volume(volume); + + if(scaled_volume != mSpeakerVolume) + { + if((scaled_volume == 0) || (mSpeakerVolume == 0)) + { + mSpeakerMuteDirty = true; + } + + mSpeakerVolume = scaled_volume; + mSpeakerVolumeDirty = true; + } } -void LLVoiceClient::declineInvite(std::string &sessionHandle) +void LLVoiceClient::setMicGain(F32 volume) { - sessionTerminateByHandle(sessionHandle); + int scaled_volume = scale_mic_volume(volume); + + if(scaled_volume != mMicVolume) + { + mMicVolume = scaled_volume; + mMicVolumeDirty = true; + } } -void LLVoiceClient::leaveNonSpatialChannel() +void LLVoiceClient::keyDown(KEY key, MASK mask) +{ +// LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL; + + if (gKeyboard->getKeyRepeated(key)) + { + // ignore auto-repeat keys + return; + } + + if(!mPTTIsMiddleMouse) + { + if(mPTTIsToggle) + { + if(key == mPTTKey) + { + toggleUserPTTState(); + } + } + else if(mPTTKey != KEY_NONE) + { + setUserPTTState(gKeyboard->getKeyDown(mPTTKey)); + } + } +} +void LLVoiceClient::keyUp(KEY key, MASK mask) { - switchChannel(mSpatialSessionURI); + if(!mPTTIsMiddleMouse) + { + if(!mPTTIsToggle && (mPTTKey != KEY_NONE)) + { + setUserPTTState(gKeyboard->getKeyDown(mPTTKey)); + } + } } - -std::string LLVoiceClient::getCurrentChannel() +void LLVoiceClient::middleMouseState(bool down) { - if((getState() == stateRunning) && !mSessionTerminateRequested) + if(mPTTIsMiddleMouse) { - return mSessionURI; + if(mPTTIsToggle) + { + if(down) + { + toggleUserPTTState(); + } + } + else + { + setUserPTTState(down); + } } - - return ""; } -bool LLVoiceClient::inProximalChannel() +///////////////////////////// +// Accessors for data related to nearby speakers +BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) { - bool result = false; - - if((getState() == stateRunning) && !mSessionTerminateRequested) + BOOL result = FALSE; + participantState *participant = findParticipantByID(id); + if(participant) { - result = !mNonSpatialChannel; + // I'm not sure what the semantics of this should be. + // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. + result = TRUE; } return result; } -std::string LLVoiceClient::sipURIFromID(const LLUUID &id) +BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) { - std::string result; - result = "sip:"; - result += nameFromID(id); - result += "@"; - result += mAccountServerName; + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = FALSE; + } + result = participant->mIsSpeaking; + } return result; } -std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) +BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id) { - std::string result; - if(avatar) + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) { - result = "sip:"; - result += nameFromID(avatar->getID()); - result += "@"; - result += mAccountServerName; + result = participant->mIsModeratorMuted; } return result; } -std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) +F32 LLVoiceClient::getCurrentPower(const LLUUID& id) +{ + F32 result = 0; + participantState *participant = findParticipantByID(id); + if(participant) { - result = nameFromID(avatar->getID()); - } + result = participant->mPower; + } + return result; } -std::string LLVoiceClient::nameFromID(const LLUUID &uuid) + +std::string LLVoiceClient::getDisplayName(const LLUUID& id) { std::string result; - // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code. - result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible - // with e-mail local-parts. - // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - result += LLBase64::encode(uuid.mData, UUID_BYTES); - LLStringUtil::replaceChar(result, '+', '-'); - LLStringUtil::replaceChar(result, '/', '_'); - - // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: - // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mDisplayName; + } return result; } -bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid) -{ - bool result = false; - - // This will only work if the name is of the proper form. - // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: - // "xFnPP04IpREWNkuw1cOXlhw==" - - if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) - { - // The name appears to have the right form. - // Reverse the transforms done by nameFromID - std::string temp = name; - LLStringUtil::replaceChar(temp, '-', '+'); - LLStringUtil::replaceChar(temp, '_', '/'); +BOOL LLVoiceClient::getUsingPTT(const LLUUID& id) +{ + BOOL result = FALSE; - U8 rawuuid[UUID_BYTES + 1]; - int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); - if(len == UUID_BYTES) - { - // The decode succeeded. Stuff the bits into the result's UUID - memcpy(uuid.mData, rawuuid, UUID_BYTES); - result = true; - } + participantState *participant = findParticipantByID(id); + if(participant) + { + // I'm not sure what the semantics of this should be. + // Does "using PTT" mean they're configured with a push-to-talk button? + // For now, we know there's no PTT mechanism in place, so nobody is using it. } return result; } -std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) +BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) { - return avatar->getFullname(); + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mOnMuteList; + } + + return result; } -std::string LLVoiceClient::sipURIFromName(std::string &name) +// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100 +// internal = 400 * external^2 +F32 LLVoiceClient::getUserVolume(const LLUUID& id) { - std::string result; - result = "sip:"; - result += name; - result += "@"; - result += mAccountServerName; - -// LLStringUtil::toLower(result); + F32 result = 0.0f; + + participantState *participant = findParticipantByID(id); + if(participant) + { + S32 ires = participant->mUserVolume; // 0-400 + result = sqrtf(((F32)ires) / 400.f); + } return result; } -///////////////////////////// -// Sending updates of current state - -void LLVoiceClient::enforceTether(void) +void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { - LLVector3d tethered = mCameraRequestedPosition; - - // constrain 'tethered' to within 50m of mAvatarPosition. + if(mAudioSession) { - F32 max_dist = 50.0f; - LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; - F32 camera_distance = (F32)camera_offset.magVec(); - if(camera_distance > max_dist) + participantState *participant = findParticipantByID(id); + if (participant) { - tethered = mAvatarPosition + - (max_dist / camera_distance) * camera_offset; + // 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; } } - - if(dist_vec(mCameraPosition, tethered) > 0.1) +} + +std::string LLVoiceClient::getGroupID(const LLUUID& id) +{ + std::string result; + + participantState *participant = findParticipantByID(id); + if(participant) { - mCameraPosition = tethered; - mSpatialCoordsDirty = true; + result = participant->mGroupID; } + + return result; } -void LLVoiceClient::updatePosition(void) +BOOL LLVoiceClient::getAreaVoiceDisabled() +{ + return mAreaVoiceDisabled; +} + +void LLVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) { +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - if(gVoiceClient) + if(!mMainSessionGroupHandle.empty()) { - LLVOAvatar *agent = gAgent.getAvatarObject(); - LLViewerRegion *region = gAgent.getRegion(); - if(region && agent) - { - LLMatrix3 rot; - LLVector3d pos; + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Start" + << "" << deltaFramesPerControlFrame << "" + << "" << "" << "" + << "false" + << "" << seconds << "" + << "\n\n\n"; - // MBW -- XXX -- Setting both camera and avatar velocity to 0 for now. May figure it out later... - // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); - pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - - gVoiceClient->setCameraPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - - // Send the current avatar position to the voice code - rot = agent->getRootJoint()->getWorldRotation().getMatrix3(); - - pos = agent->getPositionGlobal(); - // MBW -- XXX -- Can we get the head offset from outside the LLVOAvatar? -// pos += LLVector3d(mHeadOffset); - pos += LLVector3d(0.f, 0.f, 1.f); - - gVoiceClient->setAvatarPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - } + writeString(stream.str()); } } -void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +void LLVoiceClient::recordingLoopSave(const std::string& filename) { - mCameraRequestedPosition = position; - - if(mCameraVelocity != velocity) - { - mCameraVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mCameraRot != rot) +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) { - mCameraRot = rot; - mSpatialCoordsDirty = true; + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Flush" + << "" << filename << "" + << "\n\n\n"; + + writeString(stream.str()); } } -void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +void LLVoiceClient::recordingStop() { - if(dist_vec(mAvatarPosition, position) > 0.1) - { - mAvatarPosition = position; - mSpatialCoordsDirty = true; - } - - if(mAvatarVelocity != velocity) - { - mAvatarVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mAvatarRot != rot) +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) { - mAvatarRot = rot; - mSpatialCoordsDirty = true; + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Stop" + << "\n\n\n"; + + writeString(stream.str()); } } -bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) +void LLVoiceClient::filePlaybackStart(const std::string& filename) { - bool result = false; - - if(region) +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) { - name = region->getName(); + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Start" + << "" << filename << "" + << "\n\n\n"; + + writeString(stream.str()); } - - if(!name.empty()) - result = true; - - return result; } -void LLVoiceClient::leaveChannel(void) +void LLVoiceClient::filePlaybackStop() { - if(getState() == stateRunning) +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) { - LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; - mChannelName.clear(); - sessionTerminate(); + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Stop" + << "\n\n\n"; + + writeString(stream.str()); } } -void LLVoiceClient::setMuteMic(bool muted) +void LLVoiceClient::filePlaybackSetPaused(bool paused) { - mMuteMic = muted; + // TODO: Implement once Vivox gives me a sample } -void LLVoiceClient::setUserPTTState(bool ptt) +void LLVoiceClient::filePlaybackSetMode(bool vox, float speed) { - mUserPTTState = ptt; + // TODO: Implement once Vivox gives me a sample } -bool LLVoiceClient::getUserPTTState() +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) { - return mUserPTTState; } -void LLVoiceClient::toggleUserPTTState(void) +LLVoiceClient::sessionState::~sessionState() { - mUserPTTState = !mUserPTTState; + removeAllParticipants(); } -void LLVoiceClient::setVoiceEnabled(bool enabled) +LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void) { - if (enabled != mVoiceEnabled) - { - mVoiceEnabled = enabled; - if (enabled) - { - LLVoiceChannel::getCurrentVoiceChannel()->activate(); - } - else - { - // for now, leave active channel, to auto join when turning voice back on - //LLVoiceChannel::getCurrentVoiceChannel->deactivate(); - } - } + return mSessions.begin(); } -bool LLVoiceClient::voiceEnabled() +LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void) { - return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice"); + return mSessions.end(); } -void LLVoiceClient::setLipSyncEnabled(BOOL enabled) -{ - mLipSyncEnabled = enabled; -} -BOOL LLVoiceClient::lipSyncEnabled() +LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle) { - - if ( mVoiceEnabled && stateDisabled != getState() ) - { - return mLipSyncEnabled; - } - else + sessionState *result = NULL; + sessionMap::iterator iter = mSessionsByHandle.find(&handle); + if(iter != mSessionsByHandle.end()) { - return FALSE; + result = iter->second; } + + return result; } -void LLVoiceClient::setUsePTT(bool usePTT) -{ - if(usePTT && !mUsePTT) +LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) +{ + sessionState *result = NULL; + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) { - // When the user turns on PTT, reset the current state. - mUserPTTState = false; + sessionState *session = *iter; + if(session->mCreateInProgress && (session->mSIPURI == uri)) + { + result = session; + break; + } } - mUsePTT = usePTT; + + return result; } -void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) +LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id) { - if(!PTTIsToggle && mPTTIsToggle) + sessionState *result = NULL; + + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) { - // When the user turns off toggle, reset the current state. - mUserPTTState = false; + sessionState *session = *iter; + if(session->mCallerID == participant_id) + { + result = session; + break; + } } - mPTTIsToggle = PTTIsToggle; + return result; } - -void LLVoiceClient::setPTTKey(std::string &key) +LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle) { - if(key == "MiddleMouse") + sessionState *result = NULL; + + if(handle.empty()) { - mPTTIsMiddleMouse = true; + // 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 + else // (!handle.empty()) { - mPTTIsMiddleMouse = false; - if(!LLKeyboard::keyFromString(key, &mPTTKey)) + // Check for an existing session with this handle + sessionMap::iterator iter = mSessionsByHandle.find(&handle); + + if(iter != mSessionsByHandle.end()) { - // If the call failed, don't match any key. - key = KEY_NONE; + result = iter->second; } } -} -void LLVoiceClient::setEarLocation(S32 loc) -{ - if(mEarLocation != loc) + if(!result) { - LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; + // No existing session found. - mEarLocation = loc; - mSpatialCoordsDirty = true; + 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::setVoiceVolume(F32 volume) +void LLVoiceClient::setSessionHandle(sessionState *session, const std::string &handle) { - LL_DEBUGS("Voice") << "volume is " << volume << LL_ENDL; - - // 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 + // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - if(scaledVolume != mSpeakerVolume) + if(!session->mHandle.empty()) { - if((scaledVolume == -100) || (mSpeakerVolume == -100)) + // Remove session from the map if it should have been there. + sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle)); + if(iter != mSessionsByHandle.end()) { - mSpeakerMuteDirty = true; + 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; - mSpeakerVolume = scaledVolume; - mSpeakerVolumeDirty = true; + if(!handle.empty()) + { + mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session)); } + + verifySessionState(); } -void LLVoiceClient::setMicGain(F32 volume) +void LLVoiceClient::setSessionURI(sessionState *session, const std::string &uri) { - int scaledVolume = ((int)(volume * 100.0f)) - 100; - if(scaledVolume != mMicVolume) - { - mMicVolume = scaledVolume; - mMicVolumeDirty = true; - } + // There used to be a map of session URIs to sessions, which made this complex.... + session->mSIPURI = uri; + + verifySessionState(); } -void LLVoiceClient::setVivoxDebugServerName(std::string &serverName) +void LLVoiceClient::deleteSession(sessionState *session) { - if(!mAccountServerName.empty()) + // Remove the session from the handle map + if(!session->mHandle.empty()) { - // The name has been filled in already, which means we know whether we're connecting to agni or not. - if(!sConnectingToAgni) + sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle)); + if(iter != mSessionsByHandle.end()) { - // Only use the setting if we're connecting to a development grid -- always use bhr when on agni. - mAccountServerName = serverName; + if(iter->second != session) + { + LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL + } + mSessionsByHandle.erase(iter); } } -} -void LLVoiceClient::keyDown(KEY key, MASK mask) -{ - LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL; + // 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 (gKeyboard->getKeyRepeated(key)) + // If this is the current audio session, clean up the pointer which will soon be dangling. + if(mAudioSession == session) { - // ignore auto-repeat keys - return; + mAudioSession = NULL; + mAudioSessionChanged = true; } - if(!mPTTIsMiddleMouse) + // ditto for the next audio session + if(mNextAudioSession == session) { - if(mPTTIsToggle) - { - if(key == mPTTKey) - { - toggleUserPTTState(); - } - } - else if(mPTTKey != KEY_NONE) - { - setUserPTTState(gKeyboard->getKeyDown(mPTTKey)); - } + mNextAudioSession = NULL; } + + // delete the session + delete session; } -void LLVoiceClient::keyUp(KEY key, MASK mask) + +void LLVoiceClient::deleteAllSessions() { - if(!mPTTIsMiddleMouse) + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while(!mSessions.empty()) { - if(!mPTTIsToggle && (mPTTKey != KEY_NONE)) - { - setUserPTTState(gKeyboard->getKeyDown(mPTTKey)); - } + deleteSession(*(sessionsBegin())); + } + + if(!mSessionsByHandle.empty()) + { + LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL } } -void LLVoiceClient::middleMouseState(bool down) + +void LLVoiceClient::verifySessionState(void) { - if(mPTTIsMiddleMouse) + // 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++) { - if(mPTTIsToggle) + sessionState *session = *iter; + + LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL; + + if(!session->mHandle.empty()) { - if(down) + // 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()) { - toggleUserPTTState(); + 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 { - setUserPTTState(down); + 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; + } } } } -///////////////////////////// -// Accessors for data related to nearby speakers -BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) +LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) : + mURI(uri) { - BOOL result = FALSE; - participantState *participant = findParticipantByID(id); - if(participant) - { - // I'm not sure what the semantics of this should be. - // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. - result = TRUE; - } - - return result; + mOnlineSL = false; + mOnlineSLim = false; + mCanSeeMeOnline = true; + mHasBlockListEntry = false; + mHasAutoAcceptListEntry = false; + mNameResolved = false; + mInVivoxBuddies = false; + mInSLFriends = false; + mNeedsNameUpdate = false; } -BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) +void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName) { - BOOL result = FALSE; + buddyListEntry *buddy = addBuddy(uri, displayName); + buddy->mInVivoxBuddies = true; +} - participantState *participant = findParticipantByID(id); - if(participant) +LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri) +{ + std::string empty; + buddyListEntry *buddy = addBuddy(uri, empty); + if(buddy->mDisplayName.empty()) { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = FALSE; - } - result = participant->mIsSpeaking; + buddy->mNameResolved = false; } - - return result; + return buddy; } -BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id) +LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName) { - BOOL result = FALSE; + 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; + } - participantState *participant = findParticipantByID(id); - if(participant) + if(!result) { - result = participant->mIsModeratorMuted; + // 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; } -F32 LLVoiceClient::getCurrentPower(const LLUUID& id) -{ - F32 result = 0; - participantState *participant = findParticipantByID(id); - if(participant) +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter = mBuddyListMap.find(&uri); + if(iter != mBuddyListMap.end()) { - result = participant->mPower; + result = iter->second; } return result; } - -std::string LLVoiceClient::getDisplayName(const LLUUID& id) +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id) { - std::string result; - participantState *participant = findParticipantByID(id); - if(participant) + buddyListEntry *result = NULL; + buddyListMap::iterator iter; + + for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) { - result = participant->mDisplayName; + if(iter->second->mUUID == id) + { + result = iter->second; + break; + } } return result; } - -BOOL LLVoiceClient::getUsingPTT(const LLUUID& id) +LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name) { - BOOL result = FALSE; + buddyListEntry *result = NULL; + buddyListMap::iterator iter; - participantState *participant = findParticipantByID(id); - if(participant) + for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) { - // I'm not sure what the semantics of this should be. - // Does "using PTT" mean they're configured with a push-to-talk button? - // For now, we know there's no PTT mechanism in place, so nobody is using it. + if(iter->second->mDisplayName == name) + { + result = iter->second; + break; + } } return result; } -BOOL LLVoiceClient::getPTTPressed(const LLUUID& id) +void LLVoiceClient::deleteBuddy(const std::string &uri) { - BOOL result = FALSE; - - participantState *participant = findParticipantByID(id); - if(participant) + 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 { - result = participant->mPTT; + LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL; } - return result; } -BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) +void LLVoiceClient::deleteAllBuddies(void) { - BOOL result = FALSE; - - participantState *participant = findParticipantByID(id); - if(participant) + while(!mBuddyListMap.empty()) { - result = participant->mOnMuteList; + deleteBuddy(*(mBuddyListMap.begin()->first)); } - - return result; + + // 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; } -// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100 -// internal = 400 * external^2 -F32 LLVoiceClient::getUserVolume(const LLUUID& id) +void LLVoiceClient::deleteAllBlockRules(void) { - F32 result = 0.0f; - - participantState *participant = findParticipantByID(id); - if(participant) + // 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++) { - S32 ires = participant->mUserVolume; // 0-400 - result = sqrtf(((F32)ires) / 400.f); + buddy_it->second->mHasBlockListEntry = false; } - - return result; } -void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +void LLVoiceClient::deleteAllAutoAcceptRules(void) { - participantState *participant = findParticipantByID(id); - if (participant) + // 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++) { - // 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; + buddy_it->second->mHasAutoAcceptListEntry = false; } } - - -LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id) +void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly) { - serviceType result = serviceTypeUnknown; + buddyListEntry *buddy = NULL; - participantState *participant = findParticipantByID(id); - if(participant) + // blockMask is the SIP URI of a friends list entry + buddyListMap::iterator iter = mBuddyListMap.find(&blockMask); + if(iter != mBuddyListMap.end()) { - result = participant->mServiceType; + 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); } - return result; + if(buddy != NULL) + { + buddy->mHasBlockListEntry = true; + } } -std::string LLVoiceClient::getGroupID(const LLUUID& id) +void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy) { - std::string result; + buddyListEntry *buddy = NULL; - participantState *participant = findParticipantByID(id); - if(participant) + // blockMask is the SIP URI of a friends list entry + buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask); + if(iter != mBuddyListMap.end()) { - result = participant->mGroupID; + 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; } - - return result; } -BOOL LLVoiceClient::getAreaVoiceDisabled() +void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString) { - return mAreaVoiceDisabled; + // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. + mBlockRulesListReceived = true; +} + +void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString) +{ + // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. + mAutoAcceptRulesListReceived = true; } void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - mObservers.insert(observer); + mParticipantObservers.insert(observer); } void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - mObservers.erase(observer); + mParticipantObservers.erase(observer); } -void LLVoiceClient::notifyObservers() +void LLVoiceClient::notifyParticipantObservers() { - for (observer_set_t::iterator it = mObservers.begin(); - it != mObservers.end(); + for (observer_set_t::iterator it = mParticipantObservers.begin(); + it != mParticipantObservers.end(); ) { LLVoiceClientParticipantObserver* observer = *it; observer->onChange(); // In case onChange() deleted an entry. - it = mObservers.upper_bound(observer); + it = mParticipantObservers.upper_bound(observer); } } -void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer) +void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { mStatusObservers.insert(observer); } -void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer) +void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { mStatusObservers.erase(observer); } void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) + if(mAudioSession) { - switch(mVivoxErrorStatusCode) + if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mVivoxErrorStatusCode = 0; - } - - if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - //NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT - && (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408)) - { - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + switch(mAudioSession->mErrorStatusCode) + { + case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; + case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; + case 20715: + //invalid channel, we may be using a set of poorly cached + //info + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + case 1009: + //invalid username and password + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + } - // Reset the error code to make sure it won't be reused later by accident. - mVivoxErrorStatusCode = 0; + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + } + else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) + { + switch(mAudioSession->mErrorStatusCode) + { + case 404: // NOT_FOUND + case 480: // TEMPORARILY_UNAVAILABLE + case 408: // REQUEST_TIMEOUT + // call failed because other user was not available + // treat this as an error case + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + break; + } + } } - - LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session URI " << mSessionURI << LL_ENDL; + + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", session URI " << getAudioSessionURI() + << (inSpatialChannel()?", proximal is true":", proximal is false") + << LL_ENDL; for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end(); ) { LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, mSessionURI, !mNonSpatialChannel); + observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); // In case onError() deleted an entry. it = mStatusObservers.upper_bound(observer); } } +void LLVoiceClient::addObserver(LLFriendObserver* observer) +{ + mFriendObservers.insert(observer); +} + +void LLVoiceClient::removeObserver(LLFriendObserver* observer) +{ + mFriendObservers.erase(observer); +} + +void LLVoiceClient::notifyFriendObservers() +{ + for (friend_observer_set_t::iterator it = mFriendObservers.begin(); + it != mFriendObservers.end(); + ) + { + LLFriendObserver* observer = *it; + it++; + // The only friend-related thing we notify on is online/offline transitions. + observer->changed(LLFriendObserver::ONLINE); + } +} + +void LLVoiceClient::lookupName(const LLUUID &id) +{ + gCacheName->getName(id, onAvatarNameLookup); +} + //static -// void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) -// { -// participantState* statep = gVoiceClient->findParticipantByID(id); +void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) +{ + if(gVoiceClient) + { + std::string name = llformat("%s %s", first.c_str(), last.c_str()); + gVoiceClient->avatarNameResolved(id, name); + } +} -// if (statep) -// { -// statep->mDisplayName = first + " " + last; -// } +void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ + // If the avatar whose name just resolved is on our friends list, resync the friends list. + if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL) + { + mFriendsListDirty = true; + } -// gVoiceClient->notifyObservers(); -// } + // Iterate over all sessions. + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + + // Check for this user as a participant in this session + participantState *participant = session->findParticipantByID(id); + if(participant) + { + // Found -- fill in the name + participant->mAccountName = name; + // and post a "participants updated" message to listeners later. + session->mParticipantsChanged = true; + } + + // Check whether this is a p2p session whose caller name just resolved + if(session->mCallerID == id) + { + // this session's "caller ID" just resolved. Fill in the name. + session->mName = name; + if(session->mTextInvitePending) + { + session->mTextInvitePending = false; + + // We don't need to call gIMMgr->addP2PSession() here. The first incoming message will create the panel. + } + if(session->mVoiceInvitePending) + { + session->mVoiceInvitePending = false; + + gIMMgr->inviteToSession( + session->mIMSessionID, + session->mName, + session->mCallerID, + session->mName, + IM_SESSION_P2P_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE, + session->mHandle, + session->mSIPURI); + } + + } + } +} class LLViewerParcelVoiceInfo : public LLHTTPNode { diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 4ffe5edf71..43bbc8e29c 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -42,6 +42,7 @@ class LLVivoxProtocolParser; #include "v3math.h" #include "llframetimer.h" #include "llviewerregion.h" +#include "llcallingcard.h" // for LLFriendObserver class LLVoiceClientParticipantObserver { @@ -92,41 +93,10 @@ class LLVoiceClient: public LLSingleton public: - enum serviceType - { - serviceTypeUnknown, // Unknown, returned if no data on the avatar is available - serviceTypeA, // spatialized local chat - serviceTypeB, // remote multi-party chat - serviceTypeC // one-to-one and small group chat - }; static F32 OVERDRIVEN_POWER_LEVEL; void updateSettings(); // call after loading settings and whenever they change - ///////////////////////////// - // session control messages - void connect(); - - void connectorCreate(); - void connectorShutdown(); - - void requestVoiceAccountProvision(S32 retries = 3); - void userAuthorized( - const std::string& firstName, - const std::string& lastName, - const LLUUID &agentID); - void login(const std::string& accountName, const std::string &password); - void loginSendMessage(); - void logout(); - void logoutSendMessage(); - - void channelGetListSendMessage(); - void sessionCreateSendMessage(); - void sessionConnectSendMessage(); - void sessionTerminate(); - void sessionTerminateSendMessage(); - void sessionTerminateByHandle(std::string &sessionHandle); - void getCaptureDevicesSendMessage(); void getRenderDevicesSendMessage(); @@ -171,23 +141,32 @@ class LLVoiceClient: public LLSingleton ///////////////////////////// // Response/Event handlers - void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle); - void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle); - void channelGetListResponse(int statusCode, std::string &statusString); - void sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionConnectResponse(int statusCode, std::string &statusString); - void sessionTerminateResponse(int statusCode, std::string &statusString); + void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); + void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); + void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); void logoutResponse(int statusCode, std::string &statusString); void connectorShutdownResponse(int statusCode, std::string &statusString); - void loginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); - void sessionNewEvent(std::string &accountHandle, std::string &eventSessionHandle, int state, std::string &nameString, std::string &uriString); - void sessionStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, std::string &sessionHandle, int state, bool isChannel, std::string &nameString); - void participantStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, int state, std::string &nameString, std::string &displayNameString, int participantType); - void participantPropertiesEvent(std::string &uriString, int statusCode, std::string &statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); + void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); + void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); + void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming); + void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); + void sessionGroupAddedEvent(std::string &sessionGroupHandle); + void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); + void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); + void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); + void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); void auxAudioPropertiesEvent(F32 energy); - + void buddyPresenceEvent(std::string &uriString, std::string &alias, std::string &statusString, std::string &applicationString); + void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); + void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); + void subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType); + + void buddyListChanged(); void muteListChanged(); + void updateFriends(U32 mask); ///////////////////////////// // Sending updates of current state @@ -211,7 +190,6 @@ static void updatePosition(void); void setVoiceVolume(F32 volume); void setMicGain(F32 volume); void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) - void setVivoxDebugServerName(std::string &serverName); void setLipSyncEnabled(BOOL enabled); BOOL lipSyncEnabled(); @@ -226,57 +204,261 @@ static void updatePosition(void); BOOL getIsSpeaking(const LLUUID& id); BOOL getIsModeratorMuted(const LLUUID& id); F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - BOOL getPTTPressed(const LLUUID& id); // This is the inverse of the "locally muted" property. BOOL getOnMuteList(const LLUUID& id); F32 getUserVolume(const LLUUID& id); std::string getDisplayName(const LLUUID& id); // MBW -- XXX -- Not sure how to get this data out of the TVC BOOL getUsingPTT(const LLUUID& id); - serviceType getServiceType(const LLUUID& id); // type of chat the user is involved in (see bHear scope doc for definitions of A/B/C) std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) ///////////////////////////// BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. // Use this to determine whether to show a "no speech" icon in the menu bar. + + ///////////////////////////// + // Recording controls + void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); + void recordingLoopSave(const std::string& filename); + void recordingStop(); + + // Playback controls + void filePlaybackStart(const std::string& filename); + void filePlaybackStop(); + void filePlaybackSetPaused(bool paused); + void filePlaybackSetMode(bool vox = false, float speed = 1.0f); + + + // This is used by the string-keyed maps below, to avoid storing the string twice. + // The 'const std::string *' in the key points to a string actually stored in the object referenced by the map. + // The add and delete operations for each map allocate and delete in the right order to avoid dangling references. + // The default compare operation would just compare pointers, which is incorrect, so they must use this comparitor instead. + struct stringMapComparitor + { + bool operator()(const std::string* a, const std::string * b) const + { + return a->compare(*b) < 0; + } + }; + struct uuidMapComparitor + { + bool operator()(const LLUUID* a, const LLUUID * b) const + { + return *a < *b; + } + }; + struct participantState { public: participantState(const std::string &uri); + + bool updateMuteState(); + std::string mURI; - std::string mName; + LLUUID mAvatarID; + std::string mAccountName; std::string mDisplayName; - bool mPTT; - bool mIsSpeaking; - bool mIsModeratorMuted; LLFrameTimer mSpeakingTimeout; F32 mLastSpokeTimestamp; F32 mPower; int mVolume; - serviceType mServiceType; std::string mGroupID; - bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) int mUserVolume; + bool mPTT; + bool mIsSpeaking; + bool mIsModeratorMuted; + bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) bool mAvatarIDValid; - LLUUID mAvatarID; + bool mIsSelf; + }; + typedef std::map participantMap; + + typedef std::map participantUUIDMap; + + enum streamState + { + streamStateUnknown = 0, + streamStateIdle = 1, + streamStateConnected = 2, + streamStateRinging = 3, }; - typedef std::map participantMap; - participantState *findParticipant(const std::string &uri); - participantState *findParticipantByAvatar(LLVOAvatar *avatar); + struct sessionState + { + public: + sessionState(); + ~sessionState(); + + participantState *addParticipant(const std::string &uri); + // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. + // Take care not to use the pointer again after that. + void removeParticipant(participantState *participant); + void removeAllParticipants(); + + participantState *findParticipant(const std::string &uri); + participantState *findParticipantByID(const LLUUID& id); + + std::string mHandle; + std::string mGroupHandle; + std::string mSIPURI; + std::string mAlias; + std::string mName; + std::string mAlternateSIPURI; + std::string mHash; // Channel password + std::string mErrorStatusString; + std::queue mTextMsgQueue; + + LLUUID mIMSessionID; + LLUUID mCallerID; + int mErrorStatusCode; + int mMediaStreamState; + int mTextStreamState; + bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. + bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. + bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) + bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) + bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. + bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) + bool mIsSpatial; // True for spatial channels + bool mIsP2P; + bool mIncoming; + bool mVoiceEnabled; + bool mReconnect; // Whether we should try to reconnect to this session if it's dropped + // Set to true when the mute state of someone in the participant list changes. + // The code will have to walk the list to find the changed participant(s). + bool mVolumeDirty; + + bool mParticipantsChanged; + participantMap mParticipantsByURI; + participantUUIDMap mParticipantsByUUID; + }; + participantState *findParticipantByID(const LLUUID& id); - participantMap *getParticipantList(void); + + typedef std::map sessionMap; + typedef std::set sessionSet; + + typedef sessionSet::iterator sessionIterator; + sessionIterator sessionsBegin(void); + sessionIterator sessionsEnd(void); + + sessionState *findSession(const std::string &handle); + sessionState *findSessionBeingCreatedByURI(const std::string &uri); + sessionState *findSession(const LLUUID &participant_id); + sessionState *findSessionByCreateID(const std::string &create_id); + + sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null); + void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null); + void setSessionURI(sessionState *session, const std::string &uri); + void deleteSession(sessionState *session); + void deleteAllSessions(void); + + void verifySessionState(void); + + void joinedAudioSession(sessionState *session); + void leftAudioSession(sessionState *session); + + // This is called in several places where the session _may_ need to be deleted. + // It contains logic for whether to delete the session or keep it around. + void reapSession(sessionState *session); + + // Returns true if the session seems to indicate we've moved to a region on a different voice server + bool sessionNeedsRelog(sessionState *session); + + struct buddyListEntry + { + buddyListEntry(const std::string &uri); + std::string mURI; + std::string mDisplayName; + LLUUID mUUID; + bool mOnlineSL; + bool mOnlineSLim; + bool mCanSeeMeOnline; + bool mHasBlockListEntry; + bool mHasAutoAcceptListEntry; + bool mNameResolved; + bool mInSLFriends; + bool mInVivoxBuddies; + bool mNeedsNameUpdate; + }; + + typedef std::map buddyListMap; + + // This should be called when parsing a buddy list entry sent by SLVoice. + void processBuddyListEntry(const std::string &uri, const std::string &displayName); + + buddyListEntry *addBuddy(const std::string &uri); + buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName); + buddyListEntry *findBuddy(const std::string &uri); + buddyListEntry *findBuddy(const LLUUID &id); + buddyListEntry *findBuddyByDisplayName(const std::string &name); + void deleteBuddy(const std::string &uri); + void deleteAllBuddies(void); + + void deleteAllBlockRules(void); + void addBlockRule(const std::string &blockMask, const std::string &presenceOnly); + void deleteAllAutoAcceptRules(void); + void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy); + void accountListBlockRulesResponse(int statusCode, const std::string &statusString); + void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString); + + ///////////////////////////// + // session control messages + void connectorCreate(); + void connectorShutdown(); + void requestVoiceAccountProvision(S32 retries = 3); + void userAuthorized( + const std::string& firstName, + const std::string& lastName, + const LLUUID &agentID); + void login( + const std::string& account_name, + const std::string& password, + const std::string& voice_sip_uri_hostname, + const std::string& voice_account_server_uri); + void loginSendMessage(); + void logout(); + void logoutSendMessage(); + + void accountListBlockRulesSendMessage(); + void accountListAutoAcceptRulesSendMessage(); + + void sessionGroupCreateSendMessage(); + void sessionCreateSendMessage(sessionState *session, bool startAudio = true, bool startText = false); + void sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio = true, bool startText = false); + void sessionMediaConnectSendMessage(sessionState *session); // just joins the audio session + void sessionTextConnectSendMessage(sessionState *session); // just joins the text session + void sessionTerminateSendMessage(sessionState *session); + void sessionMediaDisconnectSendMessage(sessionState *session); + void sessionTextDisconnectSendMessage(sessionState *session); + + // Pokes the state machine to leave the audio session next time around. + void sessionTerminate(); + + // Pokes the state machine to shut down the connector and restart it. + void requestRelog(); + + // Does the actual work to get out of the audio session + void leaveAudioSession(); + void addObserver(LLVoiceClientParticipantObserver* observer); void removeObserver(LLVoiceClientParticipantObserver* observer); - void addStatusObserver(LLVoiceClientStatusObserver* observer); - void removeStatusObserver(LLVoiceClientStatusObserver* observer); + void addObserver(LLVoiceClientStatusObserver* observer); + void removeObserver(LLVoiceClientStatusObserver* observer); + + void addObserver(LLFriendObserver* observer); + void removeObserver(LLFriendObserver* observer); + + void lookupName(const LLUUID &id); + static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data); + void avatarNameResolved(const LLUUID &id, const std::string &name); -// static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data); typedef std::vector deviceList; deviceList *getCaptureDevices(); @@ -288,8 +470,16 @@ static void updatePosition(void); void setSpatialChannel( const std::string &uri, const std::string &credentials); - void callUser(LLUUID &uuid); - void answerInvite(std::string &sessionHandle, LLUUID& other_user_id); + // start a voice session with the specified user + void callUser(const LLUUID &uuid); + + // Send a text message to the specified user, initiating the session if necessary. + bool sendTextMessage(const LLUUID& participant_id, const std::string& message); + + // close any existing text IM session with the specified user + void endUserIMSession(const LLUUID &uuid); + + bool answerInvite(std::string &sessionHandle); void declineInvite(std::string &sessionHandle); void leaveNonSpatialChannel(); @@ -302,33 +492,37 @@ static void updatePosition(void); bool inProximalChannel(); std::string sipURIFromID(const LLUUID &id); - + + // Returns true if the indicated user is online via SIP presence according to SLVoice. + // Note that we only get SIP presence data for other users that are in our vivox buddy list. + bool isOnlineSIP(const LLUUID &id); + private: // internal state for a simple state machine. This is used to deal with the asynchronous nature of some of the messages. // Note: if you change this list, please make corresponding changes to LLVoiceClient::state2string(). enum state { + stateDisableCleanup, stateDisabled, // Voice is turned off. stateStart, // Class is initialized, socket is created stateDaemonLaunched, // Daemon has been launched stateConnecting, // connect() call has been issued + stateConnected, // connection to the daemon has been made, send some initial setup commands. stateIdle, // socket is connected, ready for messaging + stateMicTuningStart, + stateMicTuningRunning, + stateMicTuningStop, stateConnectorStart, // connector needs to be started stateConnectorStarting, // waiting for connector handle stateConnectorStarted, // connector handle received - stateMicTuningNoLogin, // mic tuning before login stateLoginRetry, // need to retry login (failed due to changing password) stateLoginRetryWait, // waiting for retry timer stateNeedsLogin, // send login request stateLoggingIn, // waiting for account handle stateLoggedIn, // account handle received + stateCreatingSessionGroup, // Creating the main session group stateNoChannel, // - stateMicTuningStart, - stateMicTuningRunning, - stateMicTuningStop, - stateSessionCreate, // need to send Session.Create command - stateSessionConnect, // need to send Session.Connect command stateJoiningSession, // waiting for session handle stateSessionJoined, // session handle received stateRunning, // in session, steady state @@ -355,7 +549,7 @@ static void updatePosition(void); state mState; bool mSessionTerminateRequested; - bool mNonSpatialChannel; + bool mRelogRequested; void setState(state inState); state getState(void) { return mState; }; @@ -378,18 +572,7 @@ static void updatePosition(void); std::string mAccountDisplayName; std::string mAccountFirstName; std::string mAccountLastName; - - std::string mNextP2PSessionURI; // URI of the P2P session to join next - std::string mNextSessionURI; // URI of the session to join next - std::string mNextSessionHandle; // Session handle of the session to join next - std::string mNextSessionHash; // Password hash for the session to join next - bool mNextSessionSpatial; // Will next session be a spatial chat? - bool mNextSessionNoReconnect; // Next session should not auto-reconnect (i.e. user -> user chat) - bool mNextSessionResetOnClose; // If this is true, go back to spatial chat when the next session terminates. - - std::string mSessionStateEventHandle; // session handle received in SessionStateChangeEvents - std::string mSessionStateEventURI; // session URI received in SessionStateChangeEvents - + bool mTuningMode; float mTuningEnergy; std::string mTuningAudioFile; @@ -400,32 +583,40 @@ static void updatePosition(void); state mTuningExitState; // state to return to when we leave tuning mode. std::string mSpatialSessionURI; - - bool mSessionResetOnClose; - - int mVivoxErrorStatusCode; - std::string mVivoxErrorStatusString; + std::string mSpatialSessionCredentials; + + std::string mMainSessionGroupHandle; // handle of the "main" session group. std::string mChannelName; // Name of the channel to be looked up bool mAreaVoiceDisabled; - std::string mSessionURI; // URI of the session we're in. - bool mSessionP2P; // true if this session is a p2p call + sessionState *mAudioSession; // Session state for the current audio session + bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. + + sessionState *mNextAudioSession; // Session state for the audio session we're trying to join + +// std::string mSessionURI; // URI of the session we're in. +// std::string mSessionHandle; // returned by ? S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings std::string mCurrentRegionName; // Used to detect parcel boundary crossings std::string mConnectorHandle; // returned by "Create Connector" message std::string mAccountHandle; // returned by login message - std::string mSessionHandle; // returned by ? + int mNumberOfAliases; U32 mCommandCookie; - std::string mAccountServerName; - std::string mAccountServerURI; + std::string mVoiceAccountServerURI; + std::string mVoiceSIPURIHostName; int mLoginRetryCount; - participantMap mParticipantMap; - bool mParticipantMapChanged; + sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. + sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. + + bool mBuddyListMapPopulated; + bool mBlockRulesListReceived; + bool mAutoAcceptRulesListReceived; + buddyListMap mBuddyListMap; deviceList mCaptureDevices; deviceList mRenderDevices; @@ -435,40 +626,41 @@ static void updatePosition(void); bool mCaptureDeviceDirty; bool mRenderDeviceDirty; - participantState *addParticipant(const std::string &uri); - // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. - // Take care not to use the pointer again after that. - void removeParticipant(participantState *participant); - void removeAllParticipants(); - - void updateMuteState(participantState *participant); - - typedef std::map channelMap; - channelMap mChannelMap; - - // These are used by the parser when processing a channel list response. - void clearChannelMap(void); - void addChannelMapEntry(std::string &name, std::string &uri); - std::string findChannelURI(std::string &name); - // This should be called when the code detects we have changed parcels. // It initiates the call to the server that gets the parcel channel. void parcelChanged(); - void switchChannel(std::string uri = std::string(), bool spatial = true, bool noReconnect = false, std::string hash = ""); - void joinSession(std::string handle, std::string uri); + void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); + void joinSession(sessionState *session); - std::string nameFromAvatar(LLVOAvatar *avatar); - std::string nameFromID(const LLUUID &id); - bool IDFromName(const std::string name, LLUUID &uuid); - std::string displayNameFromAvatar(LLVOAvatar *avatar); +static std::string nameFromAvatar(LLVOAvatar *avatar); +static std::string nameFromID(const LLUUID &id); +static bool IDFromName(const std::string name, LLUUID &uuid); +static std::string displayNameFromAvatar(LLVOAvatar *avatar); std::string sipURIFromAvatar(LLVOAvatar *avatar); std::string sipURIFromName(std::string &name); + + // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. +static std::string nameFromsipURI(const std::string &uri); + + bool inSpatialChannel(void); + std::string getAudioSessionURI(); + std::string getAudioSessionHandle(); void sendPositionalUpdate(void); void buildSetCaptureDevice(std::ostringstream &stream); void buildSetRenderDevice(std::ostringstream &stream); + void buildLocalAudioUpdates(std::ostringstream &stream); + + void clearAllLists(); + void checkFriend(const LLUUID& id); + void sendFriendsListUpdates(); + + // start a text IM session with the specified user + // This will be asynchronous, the session may be established at a future time. + sessionState* startUserIMSession(const LLUUID& uuid); + void sendQueuedTextMessages(sessionState *session); void enforceTether(void); @@ -492,10 +684,9 @@ static void updatePosition(void); bool mPTTIsToggle; bool mUserPTTState; bool mMuteMic; - - // Set to true when the mute state of someone in the participant list changes. - // The code will have to walk the list to find the changed participant(s). - bool mVolumeDirty; + + // Set to true when the friends list is known to have changed. + bool mFriendsListDirty; enum { @@ -523,14 +714,18 @@ static void updatePosition(void); BOOL mLipSyncEnabled; typedef std::set observer_set_t; - observer_set_t mObservers; + observer_set_t mParticipantObservers; - void notifyObservers(); + void notifyParticipantObservers(); typedef std::set status_observer_set_t; status_observer_set_t mStatusObservers; void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); + + typedef std::set friend_observer_set_t; + friend_observer_set_t mFriendObservers; + void notifyFriendObservers(); }; extern LLVoiceClient *gVoiceClient; diff --git a/indra/newview/skins/default/textures/slim_icon_16_viewer.tga b/indra/newview/skins/default/textures/slim_icon_16_viewer.tga new file mode 100644 index 0000000000..552181d36a Binary files /dev/null and b/indra/newview/skins/default/textures/slim_icon_16_viewer.tga differ diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 249787ad8d..c79c2aedf1 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -237,11 +237,6 @@ class WindowsManifest(ViewerManifest): # Vivox runtimes if self.prefix(src="vivox-runtime/i686-win32", dst=""): self.path("SLVoice.exe") - self.path("SLVoiceAgent.exe") - self.path("libeay32.dll") - self.path("srtp.dll") - self.path("ssleay32.dll") - self.path("tntk.dll") self.path("alut.dll") self.path("vivoxsdk.dll") self.path("ortp.dll") @@ -451,7 +446,6 @@ class DarwinManifest(ViewerManifest): self.path("vivox-runtime/universal-darwin/libortp.dylib", "libortp.dylib") self.path("vivox-runtime/universal-darwin/libvivoxsdk.dylib", "libvivoxsdk.dylib") self.path("vivox-runtime/universal-darwin/SLVoice", "SLVoice") - self.path("vivox-runtime/universal-darwin/SLVoiceAgent.app", "SLVoiceAgent.app") # llkdu dynamic library self.path("../../libraries/universal-darwin/lib_release/libllkdu.dylib", "libllkdu.dylib") -- cgit v1.2.3