summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoxanne Skelly <roxie@lindenlab.com>2009-07-15 21:50:42 +0000
committerRoxanne Skelly <roxie@lindenlab.com>2009-07-15 21:50:42 +0000
commitff52ac089f9ed67410f80fe66d0b997f0f2dafcc (patch)
treebc0f195c198ddf3d6d338e48f79e419c246f03fa
parentff51d31af3d3c28e474c2831b38bbefa6f7732c5 (diff)
DEV-34822 viewer 1.23 merge
DEV-32649 Merge the diamondware/vivox voice code -r124876 -r125220
-rw-r--r--indra/newview/CMakeLists.txt4
-rw-r--r--indra/newview/app_settings/logcontrol.xml6
-rw-r--r--indra/newview/app_settings/settings.xml55
-rw-r--r--indra/newview/llagent.cpp2
-rw-r--r--indra/newview/llappviewer.cpp7
-rw-r--r--indra/newview/llavataractions.cpp2
-rw-r--r--indra/newview/llbottomtray.cpp2
-rw-r--r--indra/newview/llcallfloater.cpp33
-rw-r--r--indra/newview/llfloaterabout.cpp14
-rw-r--r--indra/newview/llfloaterchatterbox.cpp2
-rw-r--r--indra/newview/llfloatervoicedevicesettings.cpp38
-rw-r--r--indra/newview/llimpanel.cpp14
-rw-r--r--indra/newview/llimview.cpp25
-rw-r--r--indra/newview/llinspectavatar.cpp6
-rw-r--r--indra/newview/lllogininstance.cpp1
-rw-r--r--indra/newview/lloutputmonitorctrl.cpp8
-rw-r--r--indra/newview/lloutputmonitorctrl.h2
-rw-r--r--indra/newview/lloverlaybar.cpp2
-rw-r--r--indra/newview/llpanelimcontrolpanel.cpp2
-rw-r--r--indra/newview/llparticipantlist.cpp3
-rw-r--r--indra/newview/llspeakbutton.cpp10
-rw-r--r--indra/newview/llspeakers.cpp39
-rw-r--r--indra/newview/llstartup.cpp46
-rw-r--r--indra/newview/llurl.cpp6
-rw-r--r--indra/newview/llurl.h1
-rw-r--r--indra/newview/llvieweraudio.cpp10
-rw-r--r--indra/newview/llviewercontrol.cpp4
-rw-r--r--indra/newview/llviewerwindow.cpp8
-rw-r--r--indra/newview/llvoavatar.cpp10
-rw-r--r--indra/newview/llvoicechannel.cpp64
-rw-r--r--indra/newview/llvoicechannel.h3
-rw-r--r--indra/newview/llvoiceclient.cpp6901
-rw-r--r--indra/newview/llvoiceclient.h970
-rw-r--r--indra/newview/llvoicevivox.cpp6900
-rw-r--r--indra/newview/llvoicevivox.h905
-rw-r--r--indra/newview/skins/default/xui/en/floater_about.xml2
-rwxr-xr-xindra/newview/viewer_manifest.py17
-rw-r--r--indra/viewer_components/login/lllogin.cpp1
38 files changed, 8694 insertions, 7431 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7891add9b8..cf28d9da5e 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -515,8 +515,10 @@ set(viewer_SOURCE_FILES
llvoground.cpp
llvoicechannel.cpp
llvoiceclient.cpp
+ llvoicedw.cpp
llvoiceremotectrl.cpp
llvoicevisualizer.cpp
+ llvoicevivox.cpp
llvoinventorylistener.cpp
llvopartgroup.cpp
llvosky.cpp
@@ -1018,8 +1020,10 @@ set(viewer_HEADER_FILES
llvoground.h
llvoicechannel.h
llvoiceclient.h
+ llvoicedw.h
llvoiceremotectrl.h
llvoicevisualizer.h
+ llvoicevivox.h
llvoinventorylistener.h
llvopartgroup.h
llvosky.h
diff --git a/indra/newview/app_settings/logcontrol.xml b/indra/newview/app_settings/logcontrol.xml
index 0eb98e3311..229f15852d 100644
--- a/indra/newview/app_settings/logcontrol.xml
+++ b/indra/newview/app_settings/logcontrol.xml
@@ -19,8 +19,7 @@
</array>
<key>tags</key>
<array>
- <string>AppInit</string>
- <string>LLLogin</string>
+ <string>Voice</string>
</array>
</map>
<map>
@@ -36,8 +35,7 @@
</array>
<key>tags</key>
<array>
- <string>AppInit</string>
- <string>LLLogin</string>
+ <string>Voice</string>
</array>
</map>
</array>
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 1641ab0ce1..8ca330ca33 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10312,6 +10312,17 @@
<key>Value</key>
<string></string>
</map>
+ <key>VivoxDebugSIPURIHostName</key>
+ <map>
+ <key>Comment</key>
+ <string>Hostname portion of vivox SIP URIs (empty string for the default).</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string></string>
+ </map>
<key>VivoxDebugVoiceAccountServerURI</key>
<map>
<key>Comment</key>
@@ -10323,6 +10334,28 @@
<key>Value</key>
<string></string>
</map>
+ <key>VivoxVoiceHost</key>
+ <map>
+ <key>Comment</key>
+ <string>Client SLVoice host to connect to</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>127.0.0.1</string>
+ </map>
+ <key>VivoxVoicePort</key>
+ <map>
+ <key>Comment</key>
+ <string>Client SLVoice port to connect to</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>44125</integer>
+ </map>
<key>VoiceCallsFriendsOnly</key>
<map>
<key>Comment</key>
@@ -10455,6 +10488,17 @@
<key>Value</key>
<string>Default</string>
</map>
+ <key>VoiceLogFile</key>
+ <map>
+ <key>Comment</key>
+ <string>Log file to use when launching the voice daemon</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string></string>
+ </map>
<key>VoiceOutputAudioDevice</key>
<map>
<key>Comment</key>
@@ -10499,6 +10543,17 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>VoiceServerType</key>
+ <map>
+ <key>Comment</key>
+ <string>The type of voice server to connect to.</string>
+ <key>Persist</key>
+ <integer>0</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>vivox</string>
+ </map>
<key>WLSkyDetail</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 5e2e374df6..b5cad710a8 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -5972,7 +5972,7 @@ bool LLAgent::teleportCore(bool is_local)
// MBW -- Let the voice client know a teleport has begun so it can leave the existing channel.
// This was breaking the case of teleporting within a single sim. Backing it out for now.
-// gVoiceClient->leaveChannel();
+// LLVoiceClient::getInstance()->leaveChannel();
return true;
}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index c2f8487aa9..c1d1734cfb 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -920,8 +920,7 @@ bool LLAppViewer::mainLoop()
// Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be instantiated.
- LLVoiceChannel::initClass();
- LLVoiceClient::init(gServicePump);
+ LLVoiceClient::getInstance()->init(gServicePump);
LLTimer frameTimer,idleTimer;
LLTimer debugTime;
@@ -1241,7 +1240,7 @@ bool LLAppViewer::cleanup()
// to ensure shutdown order
LLMortician::setZealous(TRUE);
- LLVoiceClient::terminate();
+ LLVoiceClient::getInstance()->terminate();
disconnectViewer();
@@ -3807,7 +3806,7 @@ void LLAppViewer::sendLogoutRequest()
gLogoutMaxTime = LOGOUT_REQUEST_TIME;
mLogoutRequestSent = TRUE;
- gVoiceClient->leaveChannel();
+ LLVoiceClient::getInstance()->leaveChannel();
//Set internal status variables and marker files
gLogoutInProgress = TRUE;
diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp
index c3deb602ee..118a546ada 100644
--- a/indra/newview/llavataractions.cpp
+++ b/indra/newview/llavataractions.cpp
@@ -290,7 +290,7 @@ bool LLAvatarActions::canCall(const LLUUID &id)
// can be only ONLINE. There is no way to see "usual resident" in OFFLINE status. If we see "usual
// resident" it automatically means that the resident is ONLINE. So to make a call to the "usual resident"
// we need to check only that "our" voice is enabled.
- return LLVoiceClient::voiceEnabled();
+ return LLVoiceClient::getInstance()->voiceEnabled();
}
}
diff --git a/indra/newview/llbottomtray.cpp b/indra/newview/llbottomtray.cpp
index 976b312509..8722ffd152 100644
--- a/indra/newview/llbottomtray.cpp
+++ b/indra/newview/llbottomtray.cpp
@@ -365,7 +365,7 @@ BOOL LLBottomTray::postBuild()
mSpeakBtn->setShowToolTip( getString("VoiceControlBtnToolTip") );
// Registering Chat Bar to receive Voice client status change notifications.
- gVoiceClient->addObserver(this);
+ LLVoiceClient::getInstance()->addObserver(this);
mObjectDefaultWidthMap[RS_BUTTON_GESTURES] = mGesturePanel->getRect().getWidth();
mObjectDefaultWidthMap[RS_BUTTON_MOVEMENT] = mMovementPanel->getRect().getWidth();
diff --git a/indra/newview/llcallfloater.cpp b/indra/newview/llcallfloater.cpp
index 1468f6d584..38782226d6 100644
--- a/indra/newview/llcallfloater.cpp
+++ b/indra/newview/llcallfloater.cpp
@@ -143,9 +143,9 @@ LLCallFloater::~LLCallFloater()
// Don't use LLVoiceClient::getInstance() here
// singleton MAY have already been destroyed.
- if(gVoiceClient)
+ if(LLVoiceClient::getInstance())
{
- gVoiceClient->removeObserver(this);
+ LLVoiceClient::getInstance()->removeObserver(this);
}
LLTransientFloaterMgr::getInstance()->removeControlView(this);
}
@@ -194,7 +194,7 @@ void LLCallFloater::draw()
// Seems this is a problem somewhere in Voice Client (LLVoiceClient::participantAddedEvent)
// onChange();
- bool is_moderator_muted = gVoiceClient->getIsModeratorMuted(gAgentID);
+ bool is_moderator_muted = LLVoiceClient::getInstance()->getIsModeratorMuted(gAgentID);
if (mIsModeratorMutedVoice != is_moderator_muted)
{
@@ -212,7 +212,6 @@ void LLCallFloater::draw()
void LLCallFloater::onChange()
{
if (NULL == mParticipants) return;
-
updateParticipantsVoiceState();
// Add newly joined participants.
@@ -252,11 +251,11 @@ void LLCallFloater::updateSession()
LLVoiceChannel* voice_channel = LLVoiceChannel::getCurrentVoiceChannel();
if (voice_channel)
{
- lldebugs << "Current voice channel: " << voice_channel->getSessionID() << llendl;
+ LL_DEBUGS("Voice") << "Current voice channel: " << voice_channel->getSessionID() << LL_ENDL;
if (mSpeakerManager && voice_channel->getSessionID() == mSpeakerManager->getSessionID())
{
- lldebugs << "Speaker manager is already set for session: " << voice_channel->getSessionID() << llendl;
+ LL_DEBUGS("Voice") << "Speaker manager is already set for session: " << voice_channel->getSessionID() << LL_ENDL;
return;
}
else
@@ -266,7 +265,7 @@ void LLCallFloater::updateSession()
}
const LLUUID& session_id = voice_channel->getSessionID();
- lldebugs << "Set speaker manager for session: " << session_id << llendl;
+ LL_DEBUGS("Voice") << "Set speaker manager for session: " << session_id << LL_ENDL;
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id);
if (im_session)
@@ -306,7 +305,7 @@ void LLCallFloater::updateSession()
{
// by default let show nearby chat participants
mSpeakerManager = LLLocalSpeakerMgr::getInstance();
- lldebugs << "Set DEFAULT speaker manager" << llendl;
+ LL_DEBUGS("Voice") << "Set DEFAULT speaker manager" << LL_ENDL;
mVoiceType = VC_LOCAL_CHAT;
}
@@ -482,16 +481,14 @@ void LLCallFloater::updateAgentModeratorState()
static void get_voice_participants_uuids(std::vector<LLUUID>& speakers_uuids)
{
// Get a list of participants from VoiceClient
- LLVoiceClient::participantMap *voice_map = gVoiceClient->getParticipantList();
- if (voice_map)
+ std::vector<LLUUID> participants = LLVoiceClient::getInstance()->getParticipantList();
+
+ for (std::vector<LLUUID>::const_iterator iter = participants.begin();
+ iter != participants.end(); ++iter)
{
- for (LLVoiceClient::participantMap::const_iterator iter = voice_map->begin();
- iter != voice_map->end(); ++iter)
- {
- LLUUID id = (*iter).second->mAvatarID;
- speakers_uuids.push_back(id);
- }
+ speakers_uuids.push_back(*iter);
}
+
}
void LLCallFloater::initParticipantsVoiceState()
@@ -567,7 +564,7 @@ void LLCallFloater::updateParticipantsVoiceState()
std::vector<LLUUID>::iterator speakers_iter = std::find(speakers_uuids.begin(), speakers_uuids.end(), participant_id);
- lldebugs << "processing speaker: " << item->getAvatarName() << ", " << item->getAvatarId() << llendl;
+ LL_DEBUGS("Voice") << "processing speaker: " << item->getAvatarName() << ", " << item->getAvatarId() << LL_ENDL;
// If an avatarID assigned to a panel is found in a speakers list
// obtained from VoiceClient we assign the JOINED status to the owner
@@ -611,7 +608,7 @@ void LLCallFloater::updateParticipantsVoiceState()
}
else
{
- llwarns << "Unsupported (" << getState(participant_id) << ") state: " << item->getAvatarName() << llendl;
+ llwarns << "Unsupported (" << getState(participant_id) << ") state: " << item->getAvatarName() << LL_ENDL;
}
}
}
diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp
index aa343b2f69..775ccfa0d8 100644
--- a/indra/newview/llfloaterabout.cpp
+++ b/indra/newview/llfloaterabout.cpp
@@ -266,8 +266,18 @@ LLSD LLFloaterAbout::getInfo()
info["J2C_VERSION"] = LLImageJ2C::getEngineInfo();
bool want_fullname = true;
info["AUDIO_DRIVER_VERSION"] = gAudiop ? LLSD(gAudiop->getDriverName(want_fullname)) : LLSD();
- info["VIVOX_VERSION"] = gVoiceClient ? gVoiceClient->getAPIVersion() : LLTrans::getString("NotConnected");
-
+ if(LLVoiceClient::getInstance()->voiceEnabled())
+ {
+ LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion();
+ std::ostringstream version_string;
+ version_string << version.serverType << " " << version.serverVersion << std::endl;
+ info["VOICE_VERSION"] = version_string.str();
+ }
+ else
+ {
+ info["VOICE_VERSION"] = LLTrans::getString("NotConnected");
+ }
+
// TODO: Implement media plugin version query
info["QT_WEBKIT_VERSION"] = "4.5.2 (version number hard-coded)";
diff --git a/indra/newview/llfloaterchatterbox.cpp b/indra/newview/llfloaterchatterbox.cpp
index 9108cfb72b..5a5d88b058 100644
--- a/indra/newview/llfloaterchatterbox.cpp
+++ b/indra/newview/llfloaterchatterbox.cpp
@@ -343,7 +343,7 @@ LLFloaterChatterBox* LLFloaterChatterBox::getInstance()
//static
LLFloater* LLFloaterChatterBox::getCurrentVoiceFloater()
{
- if (!LLVoiceClient::voiceEnabled())
+ if (!LLVoiceClient::getInstance()->voiceEnabled())
{
return NULL;
}
diff --git a/indra/newview/llfloatervoicedevicesettings.cpp b/indra/newview/llfloatervoicedevicesettings.cpp
index 43024a4bd0..0e437fb9b3 100644
--- a/indra/newview/llfloatervoicedevicesettings.cpp
+++ b/indra/newview/llfloatervoicedevicesettings.cpp
@@ -64,9 +64,6 @@ LLPanelVoiceDeviceSettings::LLPanelVoiceDeviceSettings()
// grab "live" mic volume level
mMicVolume = gSavedSettings.getF32("AudioLevelMic");
- // ask for new device enumeration
- // now do this in onOpen() instead...
- //gVoiceClient->refreshDeviceLists();
}
LLPanelVoiceDeviceSettings::~LLPanelVoiceDeviceSettings()
@@ -105,12 +102,12 @@ void LLPanelVoiceDeviceSettings::draw()
refresh();
// let user know that volume indicator is not yet available
- bool is_in_tuning_mode = gVoiceClient->inTuningMode();
+ bool is_in_tuning_mode = LLVoiceClient::getInstance()->inTuningMode();
childSetVisible("wait_text", !is_in_tuning_mode);
LLPanel::draw();
- F32 voice_power = gVoiceClient->tuningGetEnergy();
+ F32 voice_power = LLVoiceClient::getInstance()->tuningGetEnergy();
S32 discrete_power = 0;
if (!is_in_tuning_mode)
@@ -193,13 +190,13 @@ void LLPanelVoiceDeviceSettings::refresh()
LLSlider* volume_slider = getChild<LLSlider>("mic_volume_slider");
// set mic volume tuning slider based on last mic volume setting
F32 current_volume = (F32)volume_slider->getValue().asReal();
- gVoiceClient->tuningSetMicVolume(current_volume);
+ LLVoiceClient::getInstance()->tuningSetMicVolume(current_volume);
// Fill in popup menus
mCtrlInputDevices = getChild<LLComboBox>("voice_input_device");
mCtrlOutputDevices = getChild<LLComboBox>("voice_output_device");
- if(!gVoiceClient->deviceSettingsAvailable())
+ if(!LLVoiceClient::getInstance()->deviceSettingsAvailable())
{
// The combo boxes are disabled, since we can't get the device settings from the daemon just now.
// Put the currently set default (ONLY) in the box, and select it.
@@ -218,17 +215,16 @@ void LLPanelVoiceDeviceSettings::refresh()
}
else if (!mDevicesUpdated)
{
- LLVoiceClient::deviceList *devices;
-
- LLVoiceClient::deviceList::iterator iter;
+ LLVoiceDeviceList::const_iterator iter;
if(mCtrlInputDevices)
{
mCtrlInputDevices->removeall();
mCtrlInputDevices->add( getString("default_text"), ADD_BOTTOM );
- devices = gVoiceClient->getCaptureDevices();
- for(iter=devices->begin(); iter != devices->end(); iter++)
+ for(iter=LLVoiceClient::getInstance()->getCaptureDevices().begin();
+ iter != LLVoiceClient::getInstance()->getCaptureDevices().end();
+ iter++)
{
mCtrlInputDevices->add( *iter, ADD_BOTTOM );
}
@@ -244,8 +240,8 @@ void LLPanelVoiceDeviceSettings::refresh()
mCtrlOutputDevices->removeall();
mCtrlOutputDevices->add( getString("default_text"), ADD_BOTTOM );
- devices = gVoiceClient->getRenderDevices();
- for(iter=devices->begin(); iter != devices->end(); iter++)
+ for(iter= LLVoiceClient::getInstance()->getRenderDevices().begin();
+ iter != LLVoiceClient::getInstance()->getRenderDevices().end(); iter++)
{
mCtrlOutputDevices->add( *iter, ADD_BOTTOM );
}
@@ -267,34 +263,34 @@ void LLPanelVoiceDeviceSettings::initialize()
mDevicesUpdated = FALSE;
// ask for new device enumeration
- gVoiceClient->refreshDeviceLists();
+ LLVoiceClient::getInstance()->refreshDeviceLists();
// put voice client in "tuning" mode
- gVoiceClient->tuningStart();
+ LLVoiceClient::getInstance()->tuningStart();
LLVoiceChannel::suspend();
}
void LLPanelVoiceDeviceSettings::cleanup()
{
- gVoiceClient->tuningStop();
+ LLVoiceClient::getInstance()->tuningStop();
LLVoiceChannel::resume();
}
// static
void LLPanelVoiceDeviceSettings::onCommitInputDevice(LLUICtrl* ctrl, void* user_data)
{
- if(gVoiceClient)
+ if(LLVoiceClient::getInstance())
{
- gVoiceClient->setCaptureDevice(ctrl->getValue().asString());
+ LLVoiceClient::getInstance()->setCaptureDevice(ctrl->getValue().asString());
}
}
// static
void LLPanelVoiceDeviceSettings::onCommitOutputDevice(LLUICtrl* ctrl, void* user_data)
{
- if(gVoiceClient)
+ if(LLVoiceClient::getInstance())
{
- gVoiceClient->setRenderDevice(ctrl->getValue().asString());
+ LLVoiceClient::getInstance()->setRenderDevice(ctrl->getValue().asString());
}
}
diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp
index 029ddbaf2c..74e0a06a34 100644
--- a/indra/newview/llimpanel.cpp
+++ b/indra/newview/llimpanel.cpp
@@ -300,7 +300,7 @@ void LLFloaterIMPanel::onVolumeChange(LLUICtrl* source, void* user_data)
LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data;
if (floaterp)
{
- gVoiceClient->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal());
+ LLVoiceClient::getInstance()->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal());
}
}
@@ -312,7 +312,7 @@ void LLFloaterIMPanel::draw()
BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "")
&& mSessionInitialized
- && LLVoiceClient::voiceEnabled()
+ && LLVoiceClient::getInstance()->voiceEnabled()
&& mCallBackEnabled;
// hide/show start call and end call buttons
@@ -320,8 +320,8 @@ void LLFloaterIMPanel::draw()
if (!voice_channel)
return;
- childSetVisible("end_call_btn", LLVoiceClient::voiceEnabled() && voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED);
- childSetVisible("start_call_btn", LLVoiceClient::voiceEnabled() && voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED);
+ childSetVisible("end_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED);
+ childSetVisible("start_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED);
childSetEnabled("start_call_btn", enable_connect);
childSetEnabled("send_btn", !childGetValue("chat_editor").asString().empty());
@@ -384,11 +384,11 @@ void LLFloaterIMPanel::draw()
else
{
// refresh volume and mute checkbox
- childSetVisible("speaker_volume", LLVoiceClient::voiceEnabled() && voice_channel->isActive());
- childSetValue("speaker_volume", gVoiceClient->getUserVolume(mOtherParticipantUUID));
+ childSetVisible("speaker_volume", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive());
+ childSetValue("speaker_volume", LLVoiceClient::getInstance()->getUserVolume(mOtherParticipantUUID));
childSetValue("mute_btn", LLMuteList::getInstance()->isMuted(mOtherParticipantUUID, LLMute::flagVoiceChat));
- childSetVisible("mute_btn", LLVoiceClient::voiceEnabled() && voice_channel->isActive());
+ childSetVisible("mute_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive());
}
LLFloater::draw();
}
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index daabf1f717..15e657c866 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -331,13 +331,13 @@ LLIMModel::LLIMSession::~LLIMSession()
mSpeakers = NULL;
// End the text IM session if necessary
- if(gVoiceClient && mOtherParticipantID.notNull())
+ if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull())
{
switch(mType)
{
case IM_NOTHING_SPECIAL:
case IM_SESSION_P2P_INVITE:
- gVoiceClient->endUserIMSession(mOtherParticipantID);
+ LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID);
break;
default:
@@ -857,7 +857,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text,
if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id)))
{
// 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);
+ sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text);
}
if(!sent)
@@ -1723,7 +1723,11 @@ bool LLIncomingCallDialog::lifetimeHasExpired()
void LLIncomingCallDialog::onLifetimeExpired()
{
// check whether a call is valid or not
- if (LLVoiceClient::getInstance()->findSession(mPayload["caller_id"].asUUID()))
+ LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mPayload["session_id"].asUUID());
+ if(channelp &&
+ (channelp->getState() != LLVoiceChannel::STATE_NO_CHANNEL_INFO) &&
+ (channelp->getState() != LLVoiceChannel::STATE_ERROR) &&
+ (channelp->getState() != LLVoiceChannel::STATE_HUNG_UP))
{
// restart notification's timer if call is still valid
mLifetimeTimer.start();
@@ -1952,10 +1956,10 @@ void LLIncomingCallDialog::processCallResponse(S32 response)
{
if (type == IM_SESSION_P2P_INVITE)
{
- if(gVoiceClient)
+ if(LLVoiceClient::getInstance())
{
std::string s = mPayload["session_handle"].asString();
- gVoiceClient->declineInvite(s);
+ LLVoiceClient::getInstance()->declineInvite(s);
}
}
else
@@ -2043,11 +2047,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)
{
if (type == IM_SESSION_P2P_INVITE)
{
- if(gVoiceClient)
- {
- std::string s = payload["session_handle"].asString();
- gVoiceClient->declineInvite(s);
- }
+ std::string s = payload["session_handle"].asString();
+ LLVoiceClient::getInstance()->declineInvite(s);
}
else
{
@@ -3156,7 +3157,7 @@ public:
return;
}
- if(!LLVoiceClient::voiceEnabled())
+ if(!LLVoiceClient::getInstance()->voiceEnabled())
{
// Don't display voice invites unless the user has voice enabled.
return;
diff --git a/indra/newview/llinspectavatar.cpp b/indra/newview/llinspectavatar.cpp
index a2b3a54f51..ea55d25106 100644
--- a/indra/newview/llinspectavatar.cpp
+++ b/indra/newview/llinspectavatar.cpp
@@ -517,7 +517,7 @@ void LLInspectAvatar::updateVolumeSlider()
// By convention, we only display and toggle voice mutes, not all mutes
bool is_muted = LLMuteList::getInstance()->
isMuted(mAvatarID, LLMute::flagVoiceChat);
- bool voice_enabled = gVoiceClient->getVoiceEnabled(mAvatarID);
+ bool voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled(mAvatarID);
bool is_self = (mAvatarID == gAgent.getID());
LLUICtrl* mute_btn = getChild<LLUICtrl>("mute_btn");
@@ -544,7 +544,7 @@ void LLInspectAvatar::updateVolumeSlider()
else
{
// actual volume
- volume = gVoiceClient->getUserVolume(mAvatarID);
+ volume = LLVoiceClient::getInstance()->getUserVolume(mAvatarID);
// *HACK: Voice client doesn't have any data until user actually
// says something.
@@ -578,7 +578,7 @@ void LLInspectAvatar::onClickMuteVolume()
void LLInspectAvatar::onVolumeChange(const LLSD& data)
{
F32 volume = (F32)data.asReal();
- gVoiceClient->setUserVolume(mAvatarID, volume);
+ LLVoiceClient::getInstance()->setUserVolume(mAvatarID, volume);
}
void LLInspectAvatar::nameUpdatedCallback(
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 04e5cef62e..c58a220ee8 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -150,6 +150,7 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
requested_options.append("buddy-list");
requested_options.append("ui-config");
#endif
+ requested_options.append("voice-config");
requested_options.append("tutorial_setting");
requested_options.append("login-flags");
requested_options.append("global-textures");
diff --git a/indra/newview/lloutputmonitorctrl.cpp b/indra/newview/lloutputmonitorctrl.cpp
index 63803469dd..953121bd6f 100644
--- a/indra/newview/lloutputmonitorctrl.cpp
+++ b/indra/newview/lloutputmonitorctrl.cpp
@@ -119,7 +119,7 @@ void LLOutputMonitorCtrl::draw()
{
// Copied from llmediaremotectrl.cpp
// *TODO: Give the LLOutputMonitorCtrl an agent-id to monitor, then
- // call directly into gVoiceClient to ask if that agent-id is muted, is
+ // call directly into LLVoiceClient::getInstance() to ask if that agent-id is muted, is
// speaking, and what power. This avoids duplicating data, which can get
// out of sync.
const F32 LEVEL_0 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL / 3.f;
@@ -128,14 +128,14 @@ void LLOutputMonitorCtrl::draw()
if (getVisible() && mAutoUpdate && !mIsMuted && mSpeakerId.notNull())
{
- setPower(gVoiceClient->getCurrentPower(mSpeakerId));
+ setPower(LLVoiceClient::getInstance()->getCurrentPower(mSpeakerId));
if(mIsAgentControl)
{
- setIsTalking(gVoiceClient->getUserPTTState());
+ setIsTalking(LLVoiceClient::getInstance()->getUserPTTState());
}
else
{
- setIsTalking(gVoiceClient->getIsSpeaking(mSpeakerId));
+ setIsTalking(LLVoiceClient::getInstance()->getIsSpeaking(mSpeakerId));
}
}
diff --git a/indra/newview/lloutputmonitorctrl.h b/indra/newview/lloutputmonitorctrl.h
index 85ea552a57..e6dbde41db 100644
--- a/indra/newview/lloutputmonitorctrl.h
+++ b/indra/newview/lloutputmonitorctrl.h
@@ -112,7 +112,7 @@ private:
LLPointer<LLUIImage> mImageLevel2;
LLPointer<LLUIImage> mImageLevel3;
- /** whether to deal with gVoiceClient directly */
+ /** whether to deal with LLVoiceClient::getInstance() directly */
bool mAutoUpdate;
/** uuid of a speaker being monitored */
diff --git a/indra/newview/lloverlaybar.cpp b/indra/newview/lloverlaybar.cpp
index 67e048885f..3f1b23ba14 100644
--- a/indra/newview/lloverlaybar.cpp
+++ b/indra/newview/lloverlaybar.cpp
@@ -258,7 +258,7 @@ void LLOverlayBar::refresh()
{
// update "remotes"
childSetVisible("media_remote_container", TRUE);
- childSetVisible("voice_remote_container", LLVoiceClient::voiceEnabled());
+ childSetVisible("voice_remote_container", LLVoiceClient::getInstance()->voiceEnabled());
childSetVisible("state_buttons", TRUE);
}
diff --git a/indra/newview/llpanelimcontrolpanel.cpp b/indra/newview/llpanelimcontrolpanel.cpp
index b547997e7a..0111a200e8 100644
--- a/indra/newview/llpanelimcontrolpanel.cpp
+++ b/indra/newview/llpanelimcontrolpanel.cpp
@@ -92,7 +92,7 @@ BOOL LLPanelChatControlPanel::postBuild()
void LLPanelChatControlPanel::draw()
{
// hide/show start call and end call buttons
- bool voice_enabled = LLVoiceClient::voiceEnabled();
+ bool voice_enabled = LLVoiceClient::getInstance()->voiceEnabled();
LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId);
if (!session) return;
diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp
index e2da4c4475..ce946121df 100644
--- a/indra/newview/llparticipantlist.cpp
+++ b/indra/newview/llparticipantlist.cpp
@@ -336,7 +336,6 @@ void LLParticipantList::addAvatarIDExceptAgent(const LLUUID& avatar_id)
{
if (mExcludeAgent && gAgent.getID() == avatar_id) return;
if (mAvatarList->contains(avatar_id)) return;
-
mAvatarList->getIDs().push_back(avatar_id);
mAvatarList->setDirty();
adjustParticipant(avatar_id);
@@ -622,7 +621,7 @@ bool LLParticipantList::LLParticipantListMenu::enableContextMenuItem(const LLSD&
}
else if (item == "can_call")
{
- return LLVoiceClient::voiceEnabled();
+ return LLVoiceClient::getInstance()->voiceEnabled();
}
return true;
diff --git a/indra/newview/llspeakbutton.cpp b/indra/newview/llspeakbutton.cpp
index 8f2c877c7a..068ff34b36 100644
--- a/indra/newview/llspeakbutton.cpp
+++ b/indra/newview/llspeakbutton.cpp
@@ -59,9 +59,9 @@ LLSpeakButton::Params::Params()
void LLSpeakButton::draw()
{
- // gVoiceClient is the authoritative global source of info regarding our open-mic state, we merely reflect that state.
- bool openmic = gVoiceClient->getUserPTTState();
- bool voiceenabled = gVoiceClient->voiceEnabled();
+ // LLVoiceClient::getInstance() is the authoritative global source of info regarding our open-mic state, we merely reflect that state.
+ bool openmic = LLVoiceClient::getInstance()->getUserPTTState();
+ bool voiceenabled = LLVoiceClient::getInstance()->voiceEnabled();
mSpeakBtn->setToggleState(openmic && voiceenabled);
mOutputMonitor->setIsMuted(!voiceenabled);
LLUICtrl::draw();
@@ -166,11 +166,11 @@ void LLSpeakButton::setLabelVisible(bool visible)
void LLSpeakButton::onMouseDown_SpeakBtn()
{
bool down = true;
- gVoiceClient->inputUserControlState(down); // this method knows/care about whether this translates into a toggle-to-talk or down-to-talk
+ LLVoiceClient::getInstance()->inputUserControlState(down); // this method knows/care about whether this translates into a toggle-to-talk or down-to-talk
}
void LLSpeakButton::onMouseUp_SpeakBtn()
{
bool down = false;
- gVoiceClient->inputUserControlState(down);
+ LLVoiceClient::getInstance()->inputUserControlState(down);
}
diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp
index 010dfd1b33..a45c51784a 100644
--- a/indra/newview/llspeakers.cpp
+++ b/indra/newview/llspeakers.cpp
@@ -72,7 +72,7 @@ LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerTy
mDisplayName = name;
}
- gVoiceClient->setUserVolume(id, LLMuteList::getInstance()->getSavedResidentVolume(id));
+ LLVoiceClient::getInstance()->setUserVolume(id, LLMuteList::getInstance()->getSavedResidentVolume(id));
mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT);
}
@@ -210,7 +210,7 @@ LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::strin
void LLSpeakerMgr::update(BOOL resort_ok)
{
- if (!gVoiceClient)
+ if (!LLVoiceClient::getInstance())
{
return;
}
@@ -224,7 +224,7 @@ void LLSpeakerMgr::update(BOOL resort_ok)
}
// update status of all current speakers
- BOOL voice_channel_active = (!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive());
+ BOOL voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive());
for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end();)
{
LLUUID speaker_id = speaker_it->first;
@@ -232,21 +232,21 @@ void LLSpeakerMgr::update(BOOL resort_ok)
speaker_map_t::iterator cur_speaker_it = speaker_it++;
- if (voice_channel_active && gVoiceClient->getVoiceEnabled(speaker_id))
+ if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id))
{
- speakerp->mSpeechVolume = gVoiceClient->getCurrentPower(speaker_id);
- BOOL moderator_muted_voice = gVoiceClient->getIsModeratorMuted(speaker_id);
+ speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id);
+ BOOL moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id);
if (moderator_muted_voice != speakerp->mModeratorMutedVoice)
{
speakerp->mModeratorMutedVoice = moderator_muted_voice;
speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp));
}
- if (gVoiceClient->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice)
+ if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice)
{
speakerp->mStatus = LLSpeaker::STATUS_MUTED;
}
- else if (gVoiceClient->getIsSpeaking(speaker_id))
+ else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id))
{
// reset inactivity expiration
if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING)
@@ -341,19 +341,20 @@ void LLSpeakerMgr::update(BOOL resort_ok)
void LLSpeakerMgr::updateSpeakerList()
{
// are we bound to the currently active voice channel?
- if ((!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()))
+ if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()))
{
- LLVoiceClient::participantMap* participants = gVoiceClient->getParticipantList();
- if(participants)
+ std::vector<LLUUID> participants = LLVoiceClient::getInstance()->getParticipantList();
+ // add new participants to our list of known speakers
+ for (std::vector<LLUUID>::iterator participant_it = participants.begin();
+ participant_it != participants.end();
+ ++participant_it)
{
- LLVoiceClient::participantMap::iterator participant_it;
+ setSpeaker(*participant_it,
+ LLVoiceClient::getInstance()->getDisplayName(*participant_it),
+ LLSpeaker::STATUS_VOICE_ACTIVE,
+ (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL));
+
- // add new participants to our list of known speakers
- for (participant_it = participants->begin(); participant_it != participants->end(); ++participant_it)
- {
- LLVoiceClient::participantState* participantp = participant_it->second;
- setSpeaker(participantp->mAvatarID, participantp->mDisplayName, LLSpeaker::STATUS_VOICE_ACTIVE, (participantp->isAvatar()?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL));
- }
}
}
}
@@ -414,7 +415,7 @@ void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id)
BOOL LLSpeakerMgr::isVoiceActive()
{
// mVoiceChannel = NULL means current voice channel, whatever it is
- return LLVoiceClient::voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive();
+ return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive();
}
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index d1c6fca063..95158a9ae3 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -189,6 +189,7 @@
#include "llinventorybridge.h"
#include "llappearancemgr.h"
#include "llavatariconctrl.h"
+#include "llvoicechannel.h"
#include "lllogin.h"
#include "llevents.h"
@@ -1157,7 +1158,11 @@ bool idle_startup()
if(process_login_success_response())
{
// Pass the user information to the voice chat server interface.
- gVoiceClient->userAuthorized(gUserCredential->userID(), gAgentID);
+ LLVoiceClient::getInstance()->userAuthorized(gUserCredential->userID(), gAgentID);
+ // create the default proximal channel
+ LLVoiceChannel::initClass();
+ // update the voice settings
+ LLVoiceClient::getInstance()->updateSettings();
LLGridManager::getInstance()->setFavorite();
LLStartUp::setStartupState( STATE_WORLD_INIT);
}
@@ -3008,7 +3013,44 @@ bool process_login_success_response()
//setup map of datetime strings to codes and slt & local time offset from utc
LLStringOps::setupDatetimeInfo(pacific_daylight_time);
}
-
+
+ static const char* CONFIG_OPTIONS[] = {"voice-config"};
+ for (int i = 0; i < sizeof(CONFIG_OPTIONS)/sizeof(CONFIG_OPTIONS[0]); i++)
+ {
+ LLSD options = response[CONFIG_OPTIONS[i]];
+ if (!options.isArray() && (options.size() < 1) && !options[0].isMap())
+ {
+ continue;
+ }
+ llinfos << "config option " << CONFIG_OPTIONS[i][0] << "response " << options << llendl;
+ for(LLSD::map_iterator option_it = options[0].beginMap();
+ option_it != options[0].endMap();
+ option_it++)
+ {
+ llinfos << "trying option " << option_it->first << llendl;
+ LLPointer<LLControlVariable> control = gSavedSettings.getControl(option_it->first);
+ if(control.notNull())
+ {
+ if(control->isType(TYPE_BOOLEAN))
+ {
+ llinfos << "Setting BOOL from login " << option_it->first << " " << option_it->second << llendl;
+
+ gSavedSettings.setBOOL(option_it->first, !((option_it->second == "F") ||
+ (option_it->second == "false") ||
+ (!option_it->second)));
+ }
+ else if (control->isType(TYPE_STRING))
+ {
+ llinfos << "Setting String from login " << option_it->first << " " << option_it->second << llendl;
+ gSavedSettings.setString(option_it->first, option_it->second);
+ }
+ // we don't support other types now
+
+ }
+
+ }
+ }
+
LLSD initial_outfit = response["initial-outfit"][0];
if(initial_outfit.size())
{
diff --git a/indra/newview/llurl.cpp b/indra/newview/llurl.cpp
index ab65ead4c5..83a5839a93 100644
--- a/indra/newview/llurl.cpp
+++ b/indra/newview/llurl.cpp
@@ -286,5 +286,11 @@ const char * LLURL::getFullPath()
return(sReturnString);
}
+const char * LLURL::getAuthority()
+{
+ strncpy(LLURL::sReturnString,mAuthority, LL_MAX_PATH -1); /* Flawfinder: ignore */
+ LLURL::sReturnString[LL_MAX_PATH -1] = '\0';
+ return(sReturnString);
+}
char LLURL::sReturnString[LL_MAX_PATH] = "";
diff --git a/indra/newview/llurl.h b/indra/newview/llurl.h
index 9a089dd835..e41b83d29f 100644
--- a/indra/newview/llurl.h
+++ b/indra/newview/llurl.h
@@ -79,6 +79,7 @@ public:
virtual const char *getFQURL() const;
virtual const char *getFullPath();
+ virtual const char *getAuthority();
virtual const char *updateRelativePath(const LLURL &url);
diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp
index 38103f9e41..49caaf18f3 100644
--- a/indra/newview/llvieweraudio.cpp
+++ b/indra/newview/llvieweraudio.cpp
@@ -156,21 +156,21 @@ void audio_update_volume(bool force_update)
LLViewerMedia::setVolume( media_muted ? 0.0f : media_volume );
// Voice
- if (gVoiceClient)
+ if (LLVoiceClient::getInstance())
{
F32 voice_volume = gSavedSettings.getF32("AudioLevelVoice");
voice_volume = mute_volume * master_volume * voice_volume;
BOOL voice_mute = gSavedSettings.getBOOL("MuteVoice");
- gVoiceClient->setVoiceVolume(voice_mute ? 0.f : voice_volume);
- gVoiceClient->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic"));
+ LLVoiceClient::getInstance()->setVoiceVolume(voice_mute ? 0.f : voice_volume);
+ LLVoiceClient::getInstance()->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic"));
if (!gViewerWindow->getActive() && (gSavedSettings.getBOOL("MuteWhenMinimized")))
{
- gVoiceClient->setMuteMic(true);
+ LLVoiceClient::getInstance()->setMuteMic(true);
}
else
{
- gVoiceClient->setMuteMic(false);
+ LLVoiceClient::getInstance()->setMuteMic(false);
}
}
}
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 57434bd1e4..7be45c649c 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -441,9 +441,9 @@ bool handleHighResSnapshotChanged(const LLSD& newvalue)
bool handleVoiceClientPrefsChanged(const LLSD& newvalue)
{
- if(gVoiceClient)
+ if(LLVoiceClient::getInstance())
{
- gVoiceClient->updateSettings();
+ LLVoiceClient::getInstance()->updateSettings();
}
return true;
}
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index ee6fb8120c..660b33a1ff 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -813,7 +813,7 @@ BOOL LLViewerWindow::handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK m
BOOL LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask)
{
BOOL down = TRUE;
- gVoiceClient->middleMouseState(true);
+ LLVoiceClient::getInstance()->middleMouseState(true);
handleAnyMouseClick(window,pos,mask,LLMouseHandler::CLICK_MIDDLE,down);
// Always handled as far as the OS is concerned.
@@ -823,7 +823,7 @@ BOOL LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MAS
BOOL LLViewerWindow::handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask)
{
BOOL down = FALSE;
- gVoiceClient->middleMouseState(false);
+ LLVoiceClient::getInstance()->middleMouseState(false);
handleAnyMouseClick(window,pos,mask,LLMouseHandler::CLICK_MIDDLE,down);
// Always handled as far as the OS is concerned.
@@ -939,7 +939,7 @@ void LLViewerWindow::handleFocusLost(LLWindow *window)
BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated)
{
// Let the voice chat code check for its PTT key. Note that this never affects event processing.
- gVoiceClient->keyDown(key, mask);
+ LLVoiceClient::getInstance()->keyDown(key, mask);
if (gAwayTimer.getElapsedTimeF32() > MIN_AFK_TIME)
{
@@ -961,7 +961,7 @@ BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated)
BOOL LLViewerWindow::handleTranslatedKeyUp(KEY key, MASK mask)
{
// Let the voice chat code check for its PTT key. Note that this never affects event processing.
- gVoiceClient->keyUp(key, mask);
+ LLVoiceClient::getInstance()->keyUp(key, mask);
return FALSE;
}
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 6e93bf1bf2..341ea91c6d 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -1271,7 +1271,7 @@ void LLVOAvatar::initInstance(void)
//VTPause(); // VTune
- mVoiceVisualizer->setVoiceEnabled( gVoiceClient->getVoiceEnabled( mID ) );
+ mVoiceVisualizer->setVoiceEnabled( LLVoiceClient::getInstance()->getVoiceEnabled( mID ) );
}
const LLVector3 LLVOAvatar::getRenderPosition() const
@@ -2192,7 +2192,7 @@ BOOL LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time)
// store off last frame's root position to be consistent with camera position
LLVector3 root_pos_last = mRoot.getWorldPosition();
BOOL detailed_update = updateCharacter(agent);
- BOOL voice_enabled = gVoiceClient->getVoiceEnabled( mID ) && gVoiceClient->inProximalChannel();
+ BOOL voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled( mID ) && LLVoiceClient::getInstance()->inProximalChannel();
if (gNoRender)
{
@@ -2260,7 +2260,7 @@ void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled)
// Notice the calls to "gAwayTimer.reset()". This resets the timer that determines how long the avatar has been
// "away", so that the avatar doesn't lapse into away-mode (and slump over) while the user is still talking.
//-----------------------------------------------------------------------------------------------------------------
- if (gVoiceClient->getIsSpeaking( mID ))
+ if (LLVoiceClient::getInstance()->getIsSpeaking( mID ))
{
if (!mVoiceVisualizer->getCurrentlySpeaking())
{
@@ -2269,7 +2269,7 @@ void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled)
//printf( "gAwayTimer.reset();\n" );
}
- mVoiceVisualizer->setSpeakingAmplitude( gVoiceClient->getCurrentPower( mID ) );
+ mVoiceVisualizer->setSpeakingAmplitude( LLVoiceClient::getInstance()->getCurrentPower( mID ) );
if( isSelf() )
{
@@ -2498,7 +2498,7 @@ F32 LLVOAvatar::calcMorphAmount()
void LLVOAvatar::idleUpdateLipSync(bool voice_enabled)
{
// Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync
- if ( voice_enabled && (gVoiceClient->lipSyncEnabled()) && gVoiceClient->getIsSpeaking( mID ) )
+ if ( voice_enabled && (LLVoiceClient::getInstance()->lipSyncEnabled()) && LLVoiceClient::getInstance()->getIsSpeaking( mID ) )
{
F32 ooh_morph_amount = 0.0f;
F32 aah_morph_amount = 0.0f;
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index 917d69fe16..4388f473b4 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -72,9 +72,9 @@ private:
void LLVoiceCallCapResponder::error(U32 status, const std::string& reason)
{
- llwarns << "LLVoiceCallCapResponder::error("
+ LL_WARNS("Voice") << "LLVoiceCallCapResponder::error("
<< status << ": " << reason << ")"
- << llendl;
+ << LL_ENDL;
LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID);
if ( channelp )
{
@@ -104,8 +104,8 @@ void LLVoiceCallCapResponder::result(const LLSD& content)
LLSD::map_const_iterator iter;
for(iter = content.beginMap(); iter != content.endMap(); ++iter)
{
- llinfos << "LLVoiceCallCapResponder::result got "
- << iter->first << llendl;
+ LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got "
+ << iter->first << LL_ENDL;
}
channelp->setChannelInfo(
@@ -130,18 +130,16 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess
{
// a voice channel already exists for this session id, so this instance will be orphaned
// the end result should simply be the failure to make voice calls
- llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl;
+ LL_WARNS("Voice") << "Duplicate voice channels registered for session_id " << session_id << LL_ENDL;
}
-
- LLVoiceClient::getInstance()->addObserver(this);
}
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)
+ if(LLVoiceClient::getInstance())
{
- gVoiceClient->removeObserver(this);
+ LLVoiceClient::getInstance()->removeObserver(this);
}
sVoiceChannelMap.erase(mSessionID);
@@ -161,13 +159,13 @@ void LLVoiceChannel::setChannelInfo(
if (mURI.empty())
{
LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
- llwarns << "Received empty URI for channel " << mSessionName << llendl;
+ LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL;
deactivate();
}
else if (mCredentials.empty())
{
LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
- llwarns << "Received empty credentials for channel " << mSessionName << llendl;
+ LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL;
deactivate();
}
else
@@ -282,13 +280,14 @@ void LLVoiceChannel::deactivate()
//Default mic is OFF when leaving voice calls
if (gSavedSettings.getBOOL("AutoDisengageMic") &&
sCurrentVoiceChannel == this &&
- gVoiceClient->getUserPTTState())
+ LLVoiceClient::getInstance()->getUserPTTState())
{
gSavedSettings.setBOOL("PTTCurrentlyEnabled", true);
- gVoiceClient->inputUserControlState(true);
+ LLVoiceClient::getInstance()->inputUserControlState(true);
}
}
-
+ LLVoiceClient::getInstance()->removeObserver(this);
+
if (sCurrentVoiceChannel == this)
{
// default channel is proximal channel
@@ -328,7 +327,9 @@ void LLVoiceChannel::activate()
{
setState(STATE_CALL_STARTED);
}
-
+
+ LLVoiceClient::getInstance()->addObserver(this);
+
//do not send earlier, channel should be initialized, should not be in STATE_NO_CHANNEL_INFO state
sCurrentVoiceChannelChangedSignal(this->mSessionID);
}
@@ -370,6 +371,11 @@ LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri)
}
}
+LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel()
+{
+ return sCurrentVoiceChannel;
+}
+
void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id)
{
sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID));
@@ -418,7 +424,6 @@ void LLVoiceChannel::initClass()
sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance();
}
-
//static
void LLVoiceChannel::suspend()
{
@@ -434,7 +439,7 @@ void LLVoiceChannel::resume()
{
if (sSuspended)
{
- if (gVoiceClient->voiceEnabled())
+ if (LLVoiceClient::getInstance()->voiceEnabled())
{
if (sSuspendedVoiceChannel)
{
@@ -504,9 +509,9 @@ void LLVoiceChannelGroup::activate()
#endif
//Mic default state is OFF on initiating/joining Ad-Hoc/Group calls
- if (gVoiceClient->getUserPTTState() && gVoiceClient->getPTTIsToggle())
+ if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
{
- gVoiceClient->inputUserControlState(true);
+ LLVoiceClient::getInstance()->inputUserControlState(true);
}
}
@@ -553,7 +558,7 @@ void LLVoiceChannelGroup::setChannelInfo(
else
{
//*TODO: notify user
- llwarns << "Received invalid credentials for channel " << mSessionName << llendl;
+ LL_WARNS("Voice") << "Received invalid credentials for channel " << mSessionName << LL_ENDL;
deactivate();
}
}
@@ -651,7 +656,6 @@ void LLVoiceChannelGroup::setState(EState state)
LLVoiceChannelProximal::LLVoiceChannelProximal() :
LLVoiceChannel(LLUUID::null, LLStringUtil::null)
{
- activate();
}
BOOL LLVoiceChannelProximal::isActive()
@@ -663,13 +667,13 @@ void LLVoiceChannelProximal::activate()
{
if (callStarted()) return;
- LLVoiceChannel::activate();
-
- if (callStarted())
+ if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED))
{
- // this implicitly puts you back in the spatial channel
- LLVoiceClient::getInstance()->leaveNonSpatialChannel();
+ // we're connected to a non-spatial channel, so disconnect.
+ LLVoiceClient::getInstance()->leaveNonSpatialChannel();
}
+ LLVoiceChannel::activate();
+
}
void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal)
@@ -754,7 +758,7 @@ LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string
void LLVoiceChannelP2P::handleStatusChange(EStatusType type)
{
- llinfos << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << llendl;
+ LL_INFOS("Voice") << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << LL_ENDL;
// status updates
switch(type)
@@ -824,9 +828,9 @@ void LLVoiceChannelP2P::activate()
LLRecentPeople::instance().add(mOtherUserID);
//Default mic is ON on initiating/joining P2P calls
- if (!gVoiceClient->getUserPTTState() && gVoiceClient->getPTTIsToggle())
+ if (!LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
{
- gVoiceClient->inputUserControlState(true);
+ LLVoiceClient::getInstance()->inputUserControlState(true);
}
}
}
@@ -889,7 +893,7 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s
void LLVoiceChannelP2P::setState(EState state)
{
- llinfos << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << llendl;
+ LL_INFOS("Voice") << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << LL_ENDL;
if (mReceivedCall) // incoming call
{
diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h
index cb86671305..4538bfc10c 100644
--- a/indra/newview/llvoicechannel.h
+++ b/indra/newview/llvoicechannel.h
@@ -98,7 +98,8 @@ public:
static LLVoiceChannel* getChannelByID(const LLUUID& session_id);
static LLVoiceChannel* getChannelByURI(std::string uri);
- static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; }
+ static LLVoiceChannel* getCurrentVoiceChannel();
+
static void initClass();
static void suspend();
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index fc264a6fcf..0105e584c5 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -1,6 +1,6 @@
/**
* @file llvoiceclient.cpp
- * @brief Implementation of LLVoiceClient class which is the interface to the voice client process.
+ * @brief Voice client delegation class implementation.
*
* $LicenseInfo:firstyear=2001&license=viewergpl$
*
@@ -17,8 +17,7 @@
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -32,5159 +31,300 @@
#include "llviewerprecompiledheaders.h"
#include "llvoiceclient.h"
-
-#include <boost/tokenizer.hpp>
-
-// library includes
-#include "llnotificationsutil.h"
-#include "llsdutil.h"
-
-// project includes
-#include "llvoavatar.h"
-#include "llbufferstream.h"
-#include "llfile.h"
-#ifdef LL_STANDALONE
-# include "expat.h"
-#else
-# include "expat/expat.h"
-#endif
-#include "llcallbacklist.h"
-#include "llcallingcard.h" // for LLFriendObserver
-#include "llviewerregion.h"
-#include "llviewernetwork.h" // for gGridChoice
-#include "llbase64.h"
#include "llviewercontrol.h"
-#include "llkeyboard.h"
-#include "llappviewer.h" // for gDisconnected, gDisableVoice
-#include "llmutelist.h" // to check for muted avatars
-#include "llagent.h"
-#include "llcachename.h"
-#include "llimview.h" // for LLIMMgr
-#include "llparcel.h"
-#include "llviewerparcelmgr.h"
-#include "llfirstuse.h"
-#include "lltrans.h"
#include "llviewerwindow.h"
-#include "llviewercamera.h"
-#include "llvoavatarself.h"
-#include "llvoicechannel.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"
-
-// for MD5 hash
-#include "llmd5.h"
-
-#define USE_SESSION_GROUPS 0
-
-static bool sConnectingToAgni = false;
-F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
-
-const F32 SPEAKING_TIMEOUT = 1.f;
-
-const int VOICE_MAJOR_VERSION = 1;
-const int VOICE_MINOR_VERSION = 0;
-
-LLVoiceClient *gVoiceClient = NULL;
-
-// Don't retry connecting to the daemon more frequently than this:
-const F32 CONNECT_THROTTLE_SECONDS = 1.0f;
-
-// Don't send positional updates more frequently than this:
-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 to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70
- return 30 + (int)(volume * 20.0f);
-}
-
-static int scale_speaker_volume(float volume)
-{
- // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
- // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70
- return 30 + (int)(volume * 40.0f);
-}
-
-class LLViewerVoiceAccountProvisionResponder :
- public LLHTTPClient::Responder
-{
-public:
- LLViewerVoiceAccountProvisionResponder(int retries)
- {
- mRetries = retries;
- }
-
- virtual void error(U32 status, const std::string& reason)
- {
- if ( mRetries > 0 )
- {
- LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
- if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision(
- mRetries - 1);
- }
- else
- {
- LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
- if ( gVoiceClient ) gVoiceClient->giveUp();
- }
- }
-
- virtual void result(const LLSD& content)
- {
- 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(),
- voice_sip_uri_hostname,
- voice_account_server_uri);
- }
- }
-
-private:
- int mRetries;
-};
-
-/**
- * @class LLVivoxProtocolParser
- * @brief This class helps construct new LLIOPipe specializations
- * @see LLIOPipe
- *
- * THOROUGH_DESCRIPTION
- */
-class LLVivoxProtocolParser : public LLIOPipe
-{
- LOG_CLASS(LLVivoxProtocolParser);
-public:
- LLVivoxProtocolParser();
- virtual ~LLVivoxProtocolParser();
-
-protected:
- /* @name LLIOPipe virtual implementations
- */
- //@{
- /**
- * @brief Process the data in buffer
- */
- virtual EStatus process_impl(
- const LLChannelDescriptors& channels,
- buffer_ptr_t& buffer,
- bool& eos,
- LLSD& context,
- LLPumpIO* pump);
- //@}
-
- std::string mInput;
-
- // Expat control members
- XML_Parser parser;
- int responseDepth;
- bool ignoringTags;
- bool isEvent;
- 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 requestId;
- std::string actionString;
- std::string connectorHandle;
- std::string versionID;
- std::string accountHandle;
- std::string sessionHandle;
- 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;
- std::string deviceString;
- int participantType;
- bool isLocallyMuted;
- bool isModeratorMuted;
- 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;
- bool accumulateText;
-
- void reset();
-
- void processResponse(std::string tag);
-
-static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr);
-static void XMLCALL ExpatEndTag(void *data, const char *el);
-static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len);
-
- void StartTag(const char *tag, const char **attr);
- void EndTag(const char *tag);
- void CharData(const char *buffer, int length);
-
-};
-
-LLVivoxProtocolParser::LLVivoxProtocolParser()
-{
- parser = NULL;
- parser = XML_ParserCreate(NULL);
-
- reset();
-}
-
-void LLVivoxProtocolParser::reset()
-{
- responseDepth = 0;
- ignoringTags = false;
- accumulateText = false;
- energy = 0.f;
- ignoreDepth = 0;
- isChannel = false;
- isEvent = false;
- isLocallyMuted = false;
- isModeratorMuted = false;
- isSpeaking = false;
- participantType = 0;
- squelchDebugOutput = false;
- returnCode = -1;
- state = 0;
- statusCode = 0;
- volume = 0;
- textBuffer.clear();
- alias.clear();
- numberOfAliases = 0;
- applicationString.clear();
-}
-
-//virtual
-LLVivoxProtocolParser::~LLVivoxProtocolParser()
-{
- if (parser)
- XML_ParserFree(parser);
-}
-
-// virtual
-LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
- const LLChannelDescriptors& channels,
- buffer_ptr_t& buffer,
- bool& eos,
- LLSD& context,
- LLPumpIO* pump)
-{
- LLBufferStream istr(channels, buffer.get());
- std::ostringstream ostr;
- while (istr.good())
- {
- char buf[1024];
- istr.read(buf, sizeof(buf));
- mInput.append(buf, istr.gcount());
- }
-
- // 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)
- {
-
- // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)
- reset();
-
- XML_ParserReset(parser, NULL);
- XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag);
- XML_SetCharacterDataHandler(parser, ExpatCharHandler);
- 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("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL;
-
- if(!gVoiceClient->mConnected)
- {
- // If voice has been disabled, we just want to close the socket. This does so.
- LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL;
- return STATUS_STOP;
- }
-
- return STATUS_OK;
-}
-
-void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr)
-{
- if (data)
- {
- LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
- object->StartTag(el, attr);
- }
-}
-
-// --------------------------------------------------------------------------------
-
-void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el)
-{
- if (data)
- {
- LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
- object->EndTag(el);
- }
-}
-
-// --------------------------------------------------------------------------------
-
-void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len)
-{
- if (data)
- {
- LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
- object->CharData(s, len);
- }
-}
-
-// --------------------------------------------------------------------------------
+#include "llvoicedw.h"
+#include "llvoicevivox.h"
+#include "llviewernetwork.h"
+#include "llhttpnode.h"
+#include "llnotificationsutil.h"
+const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
-void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
+std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
{
- // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags
- textBuffer.clear();
- // only accumulate text if we're not ignoring tags.
- accumulateText = !ignoringTags;
-
- if (responseDepth == 0)
- {
- isEvent = !stricmp("Event", tag);
-
- if (!stricmp("Response", tag) || isEvent)
- {
- // Grab the attributes
- while (*attr)
- {
- const char *key = *attr++;
- const char *value = *attr++;
-
- if (!stricmp("requestId", key))
- {
- requestId = value;
- }
- else if (!stricmp("action", key))
- {
- actionString = value;
- }
- else if (!stricmp("type", key))
- {
- eventTypeString = value;
- }
- }
- }
- LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
- }
- else
- {
- if (ignoringTags)
- {
- LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
- }
- else
- {
- LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
+ std::string result = "UNKNOWN";
- // Ignore the InputXml stuff so we don't get confused
- if (!stricmp("InputXml", tag))
- {
- ignoringTags = true;
- ignoreDepth = responseDepth;
- accumulateText = false;
-
- LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
- }
- else if (!stricmp("CaptureDevices", tag))
- {
- gVoiceClient->clearCaptureDevices();
- }
- else if (!stricmp("RenderDevices", tag))
- {
- gVoiceClient->clearRenderDevices();
- }
- else if (!stricmp("CaptureDevice", tag))
- {
- deviceString.clear();
- }
- else if (!stricmp("RenderDevice", tag))
- {
- deviceString.clear();
- }
- else if (!stricmp("Buddies", tag))
- {
- gVoiceClient->deleteAllBuddies();
- }
- else if (!stricmp("BlockRules", tag))
- {
- gVoiceClient->deleteAllBlockRules();
- }
- else if (!stricmp("AutoAcceptRules", tag))
- {
- gVoiceClient->deleteAllAutoAcceptRules();
- }
-
- }
- }
- responseDepth++;
-}
-
-// --------------------------------------------------------------------------------
-
-void LLVivoxProtocolParser::EndTag(const char *tag)
-{
- const std::string& string = textBuffer;
-
- responseDepth--;
-
- if (ignoringTags)
- {
- if (ignoreDepth == responseDepth)
- {
- LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL;
- ignoringTags = false;
- }
- else
- {
- LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
- }
- }
+ // Prevent copy-paste errors when updating this list...
+#define CASE(x) case x: result = #x; break
- if (!ignoringTags)
+ switch(inStatus)
{
- LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
-
- // Closing a tag. Finalize the text we've accumulated and reset
- if (!stricmp("ReturnCode", tag))
- returnCode = strtol(string.c_str(), NULL, 10);
- 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 (!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 (!stricmp("VersionID", tag))
- versionID = string;
- else if (!stricmp("AccountHandle", tag))
- accountHandle = string;
- else if (!stricmp("State", tag))
- state = strtol(string.c_str(), NULL, 10);
- else if (!stricmp("URI", tag))
- uriString = string;
- 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 (!stricmp("AudioMedia", tag))
- audioMediaString = string;
- else if (!stricmp("ChannelName", tag))
- nameString = string;
- else if (!stricmp("DisplayName", tag))
- displayNameString = string;
- else if (!stricmp("Device", tag))
- deviceString = string;
- else if (!stricmp("AccountName", tag))
- nameString = string;
- else if (!stricmp("ParticipantType", tag))
- participantType = strtol(string.c_str(), NULL, 10);
- 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 (!stricmp("ChannelName", tag))
- nameString = string;
- else if (!stricmp("ChannelURI", tag))
- uriString = string;
- else if (!stricmp("BuddyURI", tag))
- uriString = string;
- else if (!stricmp("Presence", tag))
- statusString = string;
- else if (!stricmp("CaptureDevice", tag))
- {
- gVoiceClient->addCaptureDevice(deviceString);
- }
- else if (!stricmp("RenderDevice", tag))
- {
- gVoiceClient->addRenderDevice(deviceString);
- }
- 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;
-
- textBuffer.clear();
- accumulateText= false;
-
- if (responseDepth == 0)
- {
- // We finished all of the XML, process the data
- processResponse(tag);
- }
+ CASE(STATUS_LOGIN_RETRY);
+ CASE(STATUS_LOGGED_IN);
+ 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);
+ CASE(ERROR_NOT_AVAILABLE);
+ CASE(ERROR_UNKNOWN);
+ default:
+ break;
}
-}
-
-// --------------------------------------------------------------------------------
-
-void LLVivoxProtocolParser::CharData(const char *buffer, int length)
-{
- /*
- This method is called for anything that isn't a tag, which can be text you
- want that lies between tags, and a lot of stuff you don't want like file formatting
- (tabs, spaces, CR/LF, etc).
-
- Only copy text if we are in accumulate mode...
- */
- if (accumulateText)
- textBuffer.append(buffer, length);
-}
-
-// --------------------------------------------------------------------------------
-
-void LLVivoxProtocolParser::processResponse(std::string tag)
-{
- 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)
- {
- const char *eventTypeCstr = eventTypeString.c_str();
- if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent"))
- {
- gVoiceClient->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state);
- }
- else if (!stricmp(eventTypeCstr, "SessionAddedEvent"))
- {
- /*
- <Event type="SessionAddedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
- <Uri>sip:confctl-1408789@bhr.vivox.com</Uri>
- <IsChannel>true</IsChannel>
- <Incoming>false</Incoming>
- <ChannelName />
- </Event>
- */
- gVoiceClient->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString);
- }
- else if (!stricmp(eventTypeCstr, "SessionRemovedEvent"))
- {
- gVoiceClient->sessionRemovedEvent(sessionHandle, sessionGroupHandle);
- }
- else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent"))
- {
- gVoiceClient->sessionGroupAddedEvent(sessionGroupHandle);
- }
- else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent"))
- {
- /*
- <Event type="MediaStreamUpdatedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
- <StatusCode>200</StatusCode>
- <StatusString>OK</StatusString>
- <State>2</State>
- <Incoming>false</Incoming>
- </Event>
- */
- gVoiceClient->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
- }
- else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent"))
- {
- /*
- <Event type="TextStreamUpdatedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==1</SessionHandle>
- <Enabled>true</Enabled>
- <State>1</State>
- <Incoming>true</Incoming>
- </Event>
- */
- gVoiceClient->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming);
- }
- else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent"))
- {
- /*
- <Event type="ParticipantAddedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
- <ParticipantUri>sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com</ParticipantUri>
- <AccountName>xI5auBZ60SJWIk606-1JGRQ==</AccountName>
- <DisplayName />
- <ParticipantType>0</ParticipantType>
- </Event>
- */
- gVoiceClient->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType);
- }
- else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent"))
- {
- /*
- <Event type="ParticipantRemovedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
- <ParticipantUri>sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com</ParticipantUri>
- <AccountName>xtx7YNV-3SGiG7rA1fo5Ndw==</AccountName>
- </Event>
- */
- gVoiceClient->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString);
- }
- else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent"))
- {
- /*
- <Event type="ParticipantUpdatedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
- <ParticipantUri>sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com</ParticipantUri>
- <IsModeratorMuted>false</IsModeratorMuted>
- <IsSpeaking>true</IsSpeaking>
- <Volume>44</Volume>
- <Energy>0.0879437</Energy>
- </Event>
- */
-
- // These happen so often that logging them is pretty useless.
- squelchDebugOutput = true;
-
- gVoiceClient->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy);
- }
- else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
- {
- gVoiceClient->auxAudioPropertiesEvent(energy);
- }
- else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent"))
- {
- gVoiceClient->buddyPresenceEvent(uriString, alias, statusString, applicationString);
- }
- else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent"))
- {
- // The buddy list was updated during parsing.
- // Need to recheck against the friends list.
- gVoiceClient->buddyListChanged();
- }
- else if (!stricmp(eventTypeCstr, "BuddyChangedEvent"))
- {
- /*
- <Event type="BuddyChangedEvent">
- <AccountHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==</AccountHandle>
- <BuddyURI>sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com</BuddyURI>
- <DisplayName>Monroe Tester</DisplayName>
- <BuddyData />
- <GroupID>0</GroupID>
- <ChangeType>Set</ChangeType>
- </Event>
- */
- // TODO: Question: Do we need to process this at all?
- }
- else if (!stricmp(eventTypeCstr, "MessageEvent"))
- {
- gVoiceClient->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString);
- }
- else if (!stricmp(eventTypeCstr, "SessionNotificationEvent"))
- {
- gVoiceClient->sessionNotificationEvent(sessionHandle, uriString, notificationType);
- }
- else if (!stricmp(eventTypeCstr, "SubscriptionEvent"))
- {
- gVoiceClient->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType);
- }
- else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent"))
- {
- /*
- <Event type="SessionUpdatedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
- <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
- <Uri>sip:confctl-9@bhd.vivox.com</Uri>
- <IsMuted>0</IsMuted>
- <Volume>50</Volume>
- <TransmitEnabled>1</TransmitEnabled>
- <IsFocused>0</IsFocused>
- <SpeakerPosition><Position><X>0</X><Y>0</Y><Z>0</Z></Position></SpeakerPosition>
- <SessionFontID>0</SessionFontID>
- </Event>
- */
- // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
- }
-
- else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent"))
- {
- /*
- <Event type="SessionGroupRemovedEvent">
- <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
- </Event>
- */
- // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
- }
- else
- {
- LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL;
- }
- }
- else
- {
- const char *actionCstr = actionString.c_str();
- if (!stricmp(actionCstr, "Connector.Create.1"))
- {
- gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID);
- }
- else if (!stricmp(actionCstr, "Account.Login.1"))
- {
- gVoiceClient->loginResponse(statusCode, statusString, accountHandle, numberOfAliases);
- }
- else if (!stricmp(actionCstr, "Session.Create.1"))
- {
- gVoiceClient->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle);
- }
- else if (!stricmp(actionCstr, "SessionGroup.AddSession.1"))
- {
- gVoiceClient->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle);
- }
- else if (!stricmp(actionCstr, "Session.Connect.1"))
- {
- gVoiceClient->sessionConnectResponse(requestId, statusCode, statusString);
- }
- else if (!stricmp(actionCstr, "Account.Logout.1"))
- {
- gVoiceClient->logoutResponse(statusCode, statusString);
- }
- else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1"))
- {
- gVoiceClient->connectorShutdownResponse(statusCode, statusString);
- }
- else if (!stricmp(actionCstr, "Account.ListBlockRules.1"))
- {
- 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 (!stricmp(actionCstr, "Account.ChannelGetList.1"))
- {
- gVoiceClient->channelGetListResponse(statusCode, statusString);
- }
- else if (!stricmp(actionCstr, "Connector.AccountCreate.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelCreate.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelUpdate.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelDelete.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1"))
- {
-
- }
- else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1"))
- {
-
- }
-*/
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
-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
-{
-public:
- LLVoiceClientCapResponder(void){};
-
- virtual void error(U32 status, const std::string& reason); // called with bad status codes
- virtual void result(const LLSD& content);
-
-private:
-};
-
-void LLVoiceClientCapResponder::error(U32 status, const std::string& reason)
-{
- LL_WARNS("Voice") << "LLVoiceClientCapResponder::error("
- << status << ": " << reason << ")"
- << LL_ENDL;
-}
-
-void LLVoiceClientCapResponder::result(const LLSD& content)
-{
- LLSD::map_const_iterator iter;
+#undef CASE
- LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;
-
- if ( content.has("voice_credentials") )
- {
- LLSD voice_credentials = content["voice_credentials"];
- std::string uri;
- std::string credentials;
-
- if ( voice_credentials.has("channel_uri") )
- {
- uri = voice_credentials["channel_uri"].asString();
- }
- if ( voice_credentials.has("channel_credentials") )
- {
- credentials =
- voice_credentials["channel_credentials"].asString();
- }
-
- gVoiceClient->setSpatialChannel(uri, credentials);
- }
-}
-
-
-
-#if LL_WINDOWS
-static HANDLE sGatewayHandle = 0;
-
-static bool isGatewayRunning()
-{
- bool result = false;
- if(sGatewayHandle != 0)
- {
- DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0);
- if(waitresult != WAIT_OBJECT_0)
- {
- result = true;
- }
- }
return result;
}
-static void killGateway()
-{
- if(sGatewayHandle != 0)
- {
- TerminateProcess(sGatewayHandle,0);
- }
-}
-
-#else // Mac and linux
-static pid_t sGatewayPID = 0;
-static bool isGatewayRunning()
-{
- bool result = false;
- if(sGatewayPID != 0)
- {
- // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists.
- if(kill(sGatewayPID, 0) == 0)
- {
- result = true;
- }
- }
- return result;
-}
-static void killGateway()
-{
- if(sGatewayPID != 0)
- {
- kill(sGatewayPID, SIGTERM);
- }
-}
-#endif
///////////////////////////////////////////////////////////////////////////////////////////////
-LLVoiceClient::LLVoiceClient() :
- mState(stateDisabled),
- mSessionTerminateRequested(false),
- mRelogRequested(false),
- mConnected(false),
- mPump(NULL),
-
- mTuningMode(false),
- mTuningEnergy(0.0f),
- mTuningMicVolume(0),
- mTuningMicVolumeDirty(true),
- mTuningSpeakerVolume(0),
- mTuningSpeakerVolumeDirty(true),
- mTuningExitState(stateDisabled),
-
- mAreaVoiceDisabled(false),
- mAudioSession(NULL),
- mAudioSessionChanged(false),
- mNextAudioSession(NULL),
-
- mCurrentParcelLocalID(0),
- mNumberOfAliases(0),
- mCommandCookie(0),
- mLoginRetryCount(0),
-
- mBuddyListMapPopulated(false),
- mBlockRulesListReceived(false),
- mAutoAcceptRulesListReceived(false),
- mCaptureDeviceDirty(false),
- mRenderDeviceDirty(false),
- mSpatialCoordsDirty(false),
-
- mPTTDirty(true),
- mPTT(true),
- mUsePTT(true),
- mPTTIsMiddleMouse(false),
- mPTTKey(0),
- mPTTIsToggle(false),
- mUserPTTState(false),
- mMuteMic(false),
- mFriendsListDirty(true),
-
- mEarLocation(0),
- mSpeakerVolumeDirty(true),
- mSpeakerMuteDirty(true),
- mSpeakerVolume(0),
- mMicVolume(0),
- mMicVolumeDirty(true),
-
- mVoiceEnabled(false),
- mWriteInProgress(false),
-
- mLipSyncEnabled(false)
-{
- gVoiceClient = this;
-
- mAPIVersion = LLTrans::getString("NotConnected");
-
-#if LL_DARWIN || LL_LINUX || LL_SOLARIS
- // 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?
- signal(SIGPIPE, SIG_IGN);
-
- // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes.
- // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that.
- signal(SIGCHLD, SIG_IGN);
-#endif
-
- // set up state machine
- setState(stateDisabled);
-
- gIdleCallbacks.addFunction(idle, this);
+LLVoiceClient::LLVoiceClient()
+{
+ mVoiceModule = NULL;
}
//---------------------------------------------------
+// Basic setup/shutdown
LLVoiceClient::~LLVoiceClient()
{
}
-//----------------------------------------------
-
void LLVoiceClient::init(LLPumpIO *pump)
{
- // constructor will set up gVoiceClient
- LLVoiceClient::getInstance()->mPump = pump;
- LLVoiceClient::getInstance()->updateSettings();
-}
-
-void LLVoiceClient::terminate()
-{
- if(gVoiceClient)
- {
-// 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.
-
- // This will do unpleasant things on windows.
-// killGateway();
-
- // Don't do this anymore -- LLSingleton will take care of deleting the object.
-// delete gVoiceClient;
-
- // Hint to other code not to access the voice client anymore.
- gVoiceClient = NULL;
- }
-}
-
-//---------------------------------------------------
-
-void LLVoiceClient::updateSettings()
-{
- setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat"));
- setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled"));
- std::string keyString = gSavedSettings.getString("PushToTalkButton");
- setPTTKey(keyString);
- setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle"));
- setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
-
- std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
- setCaptureDevice(inputDevice);
- std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
- setRenderDevice(outputDevice);
- F32 mic_level = gSavedSettings.getF32("AudioLevelMic");
- setMicGain(mic_level);
- setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled"));
-}
-
-/////////////////////////////
-// utility functions
-
-bool LLVoiceClient::writeString(const std::string &str)
-{
- bool result = false;
- if(mConnected)
- {
- apr_status_t err;
- apr_size_t size = (apr_size_t)str.size();
- apr_size_t written = size;
-
- //MARK: Turn this on to log outgoing XML
-// LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
-
- // check return code - sockets will fail (broken, etc.)
- err = apr_socket_send(
- mSocket->getSocket(),
- (const char*)str.data(),
- &written);
-
- if(err == 0)
- {
- // Success.
- result = true;
- }
- // 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))
-// {
-// //
-// }
- else
- {
- // Assume any socket error means something bad. For now, just close the socket.
- char buf[MAX_STRING];
- LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL;
- daemonDied();
- }
- }
-
- return result;
-}
-
-
-/////////////////////////////
-// session control messages
-void LLVoiceClient::connectorCreate()
-{
- std::ostringstream stream;
- 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";
- }
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">"
- << "<ClientName>V2 SDK</ClientName>"
- << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>"
- << "<Mode>Normal</Mode>"
- << "<Logging>"
- << "<Folder>" << logpath << "</Folder>"
- << "<FileNamePrefix>Connector</FileNamePrefix>"
- << "<FileNameSuffix>.log</FileNameSuffix>"
- << "<LogLevel>" << loglevel << "</LogLevel>"
- << "</Logging>"
- << "<Application>SecondLifeViewer.1</Application>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::connectorShutdown()
-{
- setState(stateConnectorStopping);
-
- if(!mConnectorHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">"
- << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
- << "</Request>"
- << "\n\n\n";
-
- mConnectorHandle.clear();
-
- writeString(stream.str());
- }
+ // Initialize all of the voice modules
+ m_servicePump = pump;
}
void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID)
{
- LL_INFOS("Voice") << "name \"" << user_id << "\" , ID " << agentID << LL_ENDL;
-
- sConnectingToAgni = LLGridManager::getInstance()->isInProductionGrid();
-
- mAccountName = nameFromID(agentID);
-}
-
-void LLVoiceClient::requestVoiceAccountProvision(S32 retries)
-{
- if ( gAgent.getRegion() && mVoiceEnabled )
- {
- std::string url =
- gAgent.getRegion()->getCapability(
- "ProvisionVoiceAccountRequest");
-
- if ( url == "" ) return;
-
- LLHTTPClient::post(
- url,
- LLSD(),
- new LLViewerVoiceAccountProvisionResponder(retries));
- }
-}
-
-void LLVoiceClient::login(
- const std::string& account_name,
- const std::string& password,
- const std::string& voice_sip_uri_hostname,
- const std::string& voice_account_server_uri)
-{
- mVoiceSIPURIHostName = voice_sip_uri_hostname;
- mVoiceAccountServerURI = voice_account_server_uri;
-
- if(!mAccountHandle.empty())
- {
- // Already logged in.
- LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL;
-
- // Don't process another login.
- return;
- }
- else if ( account_name != mAccountName )
- {
- //TODO: error?
- 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)
-{
- LLVoiceClient* self = (LLVoiceClient*)user_data;
- self->stateMachine();
-}
-
-std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
-{
- std::string result = "UNKNOWN";
-
- // Prevent copy-paste errors when updating this list...
-#define CASE(x) case x: result = #x; break
-
- 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);
- CASE(stateLoginRetry);
- CASE(stateLoginRetryWait);
- CASE(stateNeedsLogin);
- CASE(stateLoggingIn);
- CASE(stateLoggedIn);
- CASE(stateCreatingSessionGroup);
- CASE(stateNoChannel);
- CASE(stateJoiningSession);
- CASE(stateSessionJoined);
- CASE(stateRunning);
- CASE(stateLeavingSession);
- CASE(stateSessionTerminated);
- CASE(stateLoggingOut);
- CASE(stateLoggedOut);
- CASE(stateConnectorStopping);
- CASE(stateConnectorStopped);
- CASE(stateConnectorFailed);
- CASE(stateConnectorFailedWaiting);
- CASE(stateLoginFailed);
- CASE(stateLoginFailedWaiting);
- CASE(stateJoinSessionFailed);
- CASE(stateJoinSessionFailedWaiting);
- CASE(stateJail);
- }
-
-#undef CASE
-
- return result;
-}
-
-std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
-{
- std::string result = "UNKNOWN";
-
- // Prevent copy-paste errors when updating this list...
-#define CASE(x) case x: result = #x; break
-
- switch(inStatus)
- {
- CASE(STATUS_LOGIN_RETRY);
- CASE(STATUS_LOGGED_IN);
- CASE(STATUS_JOINING);
- CASE(STATUS_JOINED);
- CASE(STATUS_LEFT_CHANNEL);
- CASE(STATUS_VOICE_DISABLED);
- CASE(STATUS_VOICE_ENABLED);
- CASE(BEGIN_ERROR_STATUS);
- CASE(ERROR_CHANNEL_FULL);
- CASE(ERROR_CHANNEL_LOCKED);
- CASE(ERROR_NOT_AVAILABLE);
- CASE(ERROR_UNKNOWN);
- default:
- break;
- }
-
-#undef CASE
-
- return result;
-}
-
-void LLVoiceClient::setState(state inState)
-{
- LL_DEBUGS("Voice") << "entering state " << state2string(inState) << LL_ENDL;
-
- mState = inState;
-}
-
-void LLVoiceClient::stateMachine()
-{
- if(gDisconnected)
- {
- // The viewer has been disconnected from the sim. Disable voice.
- setVoiceEnabled(false);
- }
-
- if(mVoiceEnabled)
- {
- updatePosition();
- }
- else if(mTuningMode)
- {
- // Tuning mode is special -- it needs to launch SLVoice even if voice is disabled.
- }
- else
- {
- if((getState() != stateDisabled) && (getState() != stateDisableCleanup))
- {
- // User turned off voice support. Send the cleanup messages, close the socket, and reset.
- if(!mConnected)
- {
- // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill.
- LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL;
- killGateway();
- }
-
- logout();
- connectorShutdown();
-
- setState(stateDisableCleanup);
- }
- }
-
- // Check for parcel boundary crossing
- {
- LLViewerRegion *region = gAgent.getRegion();
- LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
-
- if(region && parcel)
- {
- S32 parcelLocalID = parcel->getLocalID();
- std::string regionName = region->getName();
- std::string capURI = region->getCapability("ParcelVoiceInfoRequest");
-
-// LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << 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.
- // If either is empty, wait for the next time around.
- if(!regionName.empty())
- {
- if(!capURI.empty())
- {
- if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName))
- {
- // We have changed parcels. Initiate a parcel channel lookup.
- mCurrentParcelLocalID = parcelLocalID;
- mCurrentRegionName = regionName;
-
- parcelChanged();
- }
- }
- else
- {
- LL_WARNS_ONCE("Voice") << "region doesn't have ParcelVoiceInfoRequest capability. This is normal for a short time after teleporting, but bad if it persists for very long." << LL_ENDL;
- }
- }
- }
- }
-
- 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(mTuningMode || (mVoiceEnabled && !mAccountName.empty()))
- {
- setState(stateStart);
- }
- break;
-
- //MARK: stateStart
- case stateStart:
- if(gSavedSettings.getBOOL("CmdLineDisableVoice"))
- {
- // Voice is locked out, we must not launch the vivox daemon.
- setState(stateJail);
- }
- else if(!isGatewayRunning())
- {
- if(true)
- {
- // Launch the voice daemon
-
- // *FIX:Mani - Using the executable dir instead
- // of mAppRODataDir, the working directory from which the app
- // is launched.
- //std::string exe_path = gDirUtilp->getAppRODataDir();
- std::string exe_path = gDirUtilp->getExecutableDir();
- exe_path += gDirUtilp->getDirDelimiter();
-#if LL_WINDOWS
- exe_path += "SLVoice.exe";
-#elif LL_DARWIN
- exe_path += "../Resources/SLVoice";
-#else
- exe_path += "SLVoice";
-#endif
- // See if the vivox executable exists
- llstat s;
- if(!LLFile::stat(exe_path, &s))
- {
- // vivox executable exists. Build the command line and launch the daemon.
- // SLIM SDK: these arguments are no longer necessary.
-// std::string args = " -p tcp -h -c";
- std::string args;
- std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
-
- if(loglevel.empty())
- {
- loglevel = "-1"; // turn logging off completely
- }
-
- args += " -ll ";
- args += loglevel;
-
- LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL;
-
-#if LL_WINDOWS
- PROCESS_INFORMATION pinfo;
- STARTUPINFOW sinfo;
- memset(&sinfo, 0, sizeof(sinfo));
-
- std::string exe_dir = gDirUtilp->getExecutableDir();
-
- llutf16string exe_path16 = utf8str_to_utf16str(exe_path);
- llutf16string exe_dir16 = utf8str_to_utf16str(exe_dir);
- llutf16string args16 = utf8str_to_utf16str(args);
- // Create a writeable copy to keep Windows happy.
- U16 *argscpy_16 = new U16[args16.size() + 1];
- wcscpy_s(argscpy_16,args16.size()+1,args16.c_str());
- if(!CreateProcessW(exe_path16.c_str(), argscpy_16, NULL, NULL, FALSE, 0, NULL, exe_dir16.c_str(), &sinfo, &pinfo))
- {
-// DWORD dwErr = GetLastError();
- }
- else
- {
- // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
- // CloseHandle(pinfo.hProcess); // stops leaks - nothing else
- sGatewayHandle = pinfo.hProcess;
- CloseHandle(pinfo.hThread); // stops leaks - nothing else
- }
-
- delete[] argscpy_16;
-#else // LL_WINDOWS
- // This should be the same for mac and linux
- {
- std::vector<std::string> arglist;
- arglist.push_back(exe_path);
-
- // Split the argument string into separate strings for each argument
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- boost::char_separator<char> sep(" ");
- tokenizer tokens(args, sep);
- tokenizer::iterator token_iter;
-
- for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
- {
- arglist.push_back(*token_iter);
- }
-
- // create an argv vector for the child process
- char **fakeargv = new char*[arglist.size() + 1];
- int i;
- for(i=0; i < arglist.size(); i++)
- fakeargv[i] = const_cast<char*>(arglist[i].c_str());
-
- fakeargv[i] = NULL;
-
- fflush(NULL); // flush all buffers before the child inherits them
- pid_t id = vfork();
- if(id == 0)
- {
- // child
- execv(exe_path.c_str(), fakeargv);
-
- // If we reach this point, the exec failed.
- // Use _exit() instead of exit() per the vfork man page.
- _exit(0);
- }
-
- // parent
- delete[] fakeargv;
- sGatewayPID = id;
- }
-#endif // LL_WINDOWS
- mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost").c_str(), gSavedSettings.getU32("VoicePort"));
- }
- else
- {
- 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:44125
- // and put that host's IP address here.
- mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost"), gSavedSettings.getU32("VoicePort"));
- }
-
- mUpdateTimer.start();
- mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
-
- setState(stateDaemonLaunched);
-
- // 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:
- if(mUpdateTimer.hasExpired())
- {
- LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;
-
- mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
-
- if(!mSocket)
- {
- mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
- }
-
- mConnected = mSocket->blockingConnect(mDaemonHost);
- if(mConnected)
- {
- setState(stateConnecting);
- }
- else
- {
- // If the connect failed, the socket may have been put into a bad state. Delete it.
- closeSocket();
- }
- }
- break;
-
- //MARK: stateConnecting
- case stateConnecting:
- // Can't do this until we have the pump available.
- if(mPump)
- {
- // MBW -- Note to self: pumps and pipes examples in
- // indra/test/io.cpp
- // indra/test/llpipeutil.{cpp|h}
-
- // Attach the pumps and pipes
-
- LLPumpIO::chain_t readChain;
-
- readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket)));
- readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser()));
-
- mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);
-
- setState(stateConnected);
- }
-
- break;
-
- //MARK: stateConnected
- case stateConnected:
- // Initial devices query
- getCaptureDevicesSendMessage();
- getRenderDevicesSendMessage();
-
- mLoginRetryCount = 0;
-
- setState(stateIdle);
- break;
-
- //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 = stateIdle;
- setState(stateMicTuningStart);
- }
- else if(!mVoiceEnabled)
- {
- // We never started up the connector. This will shut down the daemon.
- setState(stateConnectorStopped);
- }
- else if(!mAccountName.empty())
- {
- LLViewerRegion *region = gAgent.getRegion();
-
- if(region)
- {
- if ( region->getCapability("ProvisionVoiceAccountRequest") != "" )
- {
- if ( mAccountPassword.empty() )
- {
- requestVoiceAccountProvision();
- }
- setState(stateConnectorStart);
- }
- else
- {
- LL_WARNS_ONCE("Voice") << "region doesn't have ProvisionVoiceAccountRequest capability!" << LL_ENDL;
- }
- }
- }
- break;
-
- //MARK: stateMicTuningStart
- case stateMicTuningStart:
- if(mUpdateTimer.hasExpired())
- {
- if(mCaptureDeviceDirty || mRenderDeviceDirty)
- {
- // These can't be changed while in tuning mode. Set them before starting.
- std::ostringstream stream;
-
- buildSetCaptureDevice(stream);
- buildSetRenderDevice(stream);
-
- if(!stream.str().empty())
- {
- writeString(stream.str());
- }
-
- // This will come around again in the same state and start the capture, after the timer expires.
- mUpdateTimer.start();
- mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
- }
- else
- {
- // duration parameter is currently unused, per Mike S.
- tuningCaptureStartSendMessage(10000);
-
- setState(stateMicTuningRunning);
- }
- }
-
- break;
-
- //MARK: stateMicTuningRunning
- case stateMicTuningRunning:
- if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty)
- {
- // All of these conditions make us leave tuning mode.
- setState(stateMicTuningStop);
- }
- else
- {
- // process mic/speaker volume changes
- if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty)
- {
- std::ostringstream stream;
-
- if(mTuningMicVolumeDirty)
- {
- LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">"
- << "<Level>" << mTuningMicVolume << "</Level>"
- << "</Request>\n\n\n";
- }
-
- if(mTuningSpeakerVolumeDirty)
- {
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">"
- << "<Level>" << mTuningSpeakerVolume << "</Level>"
- << "</Request>\n\n\n";
- }
-
- mTuningMicVolumeDirty = false;
- mTuningSpeakerVolumeDirty = false;
-
- if(!stream.str().empty())
- {
- writeString(stream.str());
- }
- }
- }
- break;
-
- //MARK: stateMicTuningStop
- case stateMicTuningStop:
- {
- // transition out of mic tuning
- tuningCaptureStopSendMessage();
-
- setState(mTuningExitState);
-
- // if we exited just to change devices, this will keep us from re-entering too fast.
- mUpdateTimer.start();
- mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
-
- }
- 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)
- {
- // First retry -- display a message to the user
- notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY);
- }
-
- mLoginRetryCount++;
-
- if(mLoginRetryCount > MAX_LOGIN_RETRIES)
- {
- LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL;
- setState(stateLoginFailed);
- }
- else
- {
- LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL;
- mUpdateTimer.start();
- mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS);
- setState(stateLoginRetryWait);
- }
- break;
-
- //MARK: stateLoginRetryWait
- case stateLoginRetryWait:
- if(mUpdateTimer.hasExpired())
- {
- setState(stateNeedsLogin);
- }
- break;
-
- //MARK: stateNeedsLogin
- case stateNeedsLogin:
- if(!mAccountPassword.empty())
- {
- setState(stateLoggingIn);
- loginSendMessage();
- }
- break;
-
- //MARK: stateLoggingIn
- case stateLoggingIn: // waiting for account handle
- // loginResponse() will transition from here to stateLoggedIn.
- break;
-
- //MARK: stateLoggedIn
- case stateLoggedIn: // account handle received
-
- 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))
- {
- LLMuteList::getInstance()->addObserver(&mutelist_listener);
- sMuteListListener_listening = true;
- }
-
- // Set up the friends list observer if it hasn't been set up already.
- if(friendslist_listener == NULL)
- {
- friendslist_listener = new LLVoiceClientFriendsObserver;
- LLAvatarTracker::instance().addObserver(friendslist_listener);
- }
-
- // Set the initial state of mic mute, local speaker volume, etc.
- {
- std::ostringstream stream;
-
- buildLocalAudioUpdates(stream);
-
- if(!stream.str().empty())
- {
- writeString(stream.str());
- }
- }
-
-#if USE_SESSION_GROUPS
- // create the main session group
- sessionGroupCreateSendMessage();
-
- setState(stateCreatingSessionGroup);
-#else
- // Not using session groups -- skip the stateCreatingSessionGroup state.
- setState(stateNoChannel);
-
- // Initial kick-off of channel lookup logic
- parcelChanged();
-#endif
- break;
-
- //MARK: stateCreatingSessionGroup
- case stateCreatingSessionGroup:
- if(mSessionTerminateRequested || !mVoiceEnabled)
- {
- // TODO: Question: is this the right way out of this state
- setState(stateSessionTerminated);
- }
- else if(!mMainSessionGroupHandle.empty())
- {
- setState(stateNoChannel);
-
- // Start looped recording (needed for "panic button" anti-griefing tool)
- recordingLoopStart();
-
- // Initial kick-off of channel lookup logic
- parcelChanged();
- }
- break;
-
- //MARK: stateNoChannel
- case stateNoChannel:
- // Do this here as well as inside sendPositionalUpdate().
- // Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync.
- sendFriendsListUpdates();
-
- if(mSessionTerminateRequested || !mVoiceEnabled)
- {
- // 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
- // joinedAudioSession() will transition from here to stateSessionJoined.
- if(!mVoiceEnabled)
- {
- // User bailed out during connect -- jump straight to teardown.
- setState(stateSessionTerminated);
- }
- else if(mSessionTerminateRequested)
- {
- 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(mAudioSession->mIsP2P)
- {
- sessionMediaDisconnectSendMessage(mAudioSession);
- setState(stateSessionTerminated);
- }
- }
- }
- break;
-
- //MARK: stateSessionJoined
- case stateSessionJoined: // session handle received
- // 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(mAudioSession && mAudioSession->mVoiceEnabled)
- {
- // Dirty state that may need to be sync'ed with the daemon.
- mPTTDirty = true;
- mSpeakerVolumeDirty = true;
- mSpatialCoordsDirty = true;
-
- setState(stateRunning);
-
- // 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)
- {
- // User bailed out during connect -- jump straight to teardown.
- setState(stateSessionTerminated);
- }
- else if(mSessionTerminateRequested)
- {
- // 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(mAudioSession && mAudioSession->mIsP2P)
- {
- sessionMediaDisconnectSendMessage(mAudioSession);
- setState(stateSessionTerminated);
- }
- }
- break;
-
- //MARK: stateRunning
- case stateRunning: // steady state
- // Disabling voice or disconnect requested.
- if(!mVoiceEnabled || mSessionTerminateRequested)
- {
- leaveAudioSession();
- }
- else
- {
-
- // Figure out whether the PTT state needs to change
- {
- bool newPTT;
- if(mUsePTT)
- {
- // If configured to use PTT, track the user state.
- newPTT = mUserPTTState;
- }
- else
- {
- // If not configured to use PTT, it should always be true (otherwise the user will be unable to speak).
- newPTT = true;
- }
-
- if(mMuteMic)
- {
- // This always overrides any other PTT setting.
- newPTT = false;
- }
-
- // Dirty if state changed.
- if(newPTT != mPTT)
- {
- mPTT = newPTT;
- mPTTDirty = true;
- }
- }
-
- if(!inSpatialChannel())
- {
- // When in a non-spatial channel, never send positional updates.
- mSpatialCoordsDirty = false;
- }
- else
- {
- // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position)
- enforceTether();
- }
-
- // 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((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
- {
- mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
- sendPositionalUpdate();
- }
- }
- 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:
-
- // 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;
-
- // The old session may now need to be deleted.
- reapSession(oldSession);
- }
- else
- {
- 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);
- }
- else
- {
- // 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 AccountLoginStateChangeEvent will transition from here to stateLoggedOut.
- break;
-
- //MARK: stateLoggedOut
- case stateLoggedOut: // logout response received
-
- // Once we're logged out, all these things are invalid.
- mAccountHandle.clear();
- deleteAllSessions();
- deleteAllBuddies();
-
- if(mVoiceEnabled && !mRelogRequested)
- {
- // User was logged out, but wants to be logged in. Send a new login request.
- setState(stateNeedsLogin);
- }
- else
- {
- // 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
- setState(stateDisableCleanup);
- break;
-
- //MARK: stateConnectorFailed
- case stateConnectorFailed:
- setState(stateConnectorFailedWaiting);
- break;
- //MARK: stateConnectorFailedWaiting
- case stateConnectorFailedWaiting:
- if(!mVoiceEnabled)
- {
- setState(stateDisableCleanup);
- }
- break;
-
- //MARK: stateLoginFailed
- case stateLoginFailed:
- setState(stateLoginFailedWaiting);
- break;
- //MARK: stateLoginFailedWaiting
- case stateLoginFailedWaiting:
- if(!mVoiceEnabled)
- {
- setState(stateDisableCleanup);
- }
- break;
-
- //MARK: stateJoinSessionFailed
- case stateJoinSessionFailed:
- // Transition to error state. Send out any notifications here.
- 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.
- if(mSessionTerminateRequested)
- {
- setState(stateSessionTerminated);
- }
- break;
-
- //MARK: stateJail
- case stateJail:
- // We have given up. Do nothing.
- break;
-
- }
-
- if(mAudioSession && mAudioSession->mParticipantsChanged)
- {
- mAudioSession->mParticipantsChanged = false;
- mAudioSessionChanged = true;
- }
-
- if(mAudioSessionChanged)
- {
- mAudioSessionChanged = false;
- notifyParticipantObservers();
- }
-}
-
-void LLVoiceClient::closeSocket(void)
-{
- mSocket.reset();
- mConnected = false;
-}
-
-void LLVoiceClient::loginSendMessage()
-{
- std::ostringstream stream;
-
- bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps");
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"
- << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
- << "<AccountName>" << mAccountName << "</AccountName>"
- << "<AccountPassword>" << mAccountPassword << "</AccountPassword>"
- << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>"
- << "<EnableBuddiesAndPresence>true</EnableBuddiesAndPresence>"
- << "<BuddyManagementMode>Application</BuddyManagementMode>"
- << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>"
- << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"")
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::logout()
-{
- // Ensure that we'll re-request provisioning before logging in again
- mAccountPassword.clear();
- mVoiceAccountServerURI.clear();
-
- setState(stateLoggingOut);
- logoutSendMessage();
-}
-
-void LLVoiceClient::logoutSendMessage()
-{
- if(!mAccountHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "</Request>"
- << "\n\n\n";
-
- mAccountHandle.clear();
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::accountListBlockRulesSendMessage()
-{
- if(!mAccountHandle.empty())
- {
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL;
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListBlockRules.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "</Request>"
- << "\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::accountListAutoAcceptRulesSendMessage()
-{
- if(!mAccountHandle.empty())
- {
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL;
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListAutoAcceptRules.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "</Request>"
- << "\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::sessionGroupCreateSendMessage()
-{
- if(!mAccountHandle.empty())
- {
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "creating session group" << LL_ENDL;
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<Type>Normal</Type>"
- << "</Request>"
- << "\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText)
-{
- LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
-
- session->mCreateInProgress = true;
- if(startAudio)
- {
- session->mMediaConnectInProgress = true;
- }
-
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<URI>" << session->mSIPURI << "</URI>";
-
- static const std::string allowed_chars =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- "0123456789"
- "-._~";
-
- if(!session->mHash.empty())
- {
- stream
- << "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"
- << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
- }
-
- stream
- << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
- << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
- << "<Name>" << mChannelName << "</Name>"
- << "</Request>\n\n\n";
- writeString(stream.str());
-}
-
-void LLVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)
-{
- LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
-
- session->mCreateInProgress = true;
- if(startAudio)
+ // In the future, we should change this to allow voice module registration
+ // with a table lookup of sorts.
+ std::string voice_server = gSavedSettings.getString("VoiceServerType");
+ LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL;
+ if(voice_server == "diamondware")
{
- session->mMediaConnectInProgress = true;
+ mVoiceModule = (LLVoiceModuleInterface *)LLDiamondwareVoiceClient::getInstance();
}
-
- std::string password;
- if(!session->mHash.empty())
- {
- static const std::string allowed_chars =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- "0123456789"
- "-._~"
- ;
- password = LLURI::escape(session->mHash, allowed_chars);
- }
-
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">"
- << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
- << "<URI>" << session->mSIPURI << "</URI>"
- << "<Name>" << mChannelName << "</Name>"
- << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
- << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
- << "<Password>" << password << "</Password>"
- << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"
- << "</Request>\n\n\n"
- ;
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
-{
- LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL;
-
- session->mMediaConnectInProgress = true;
-
- std::ostringstream stream;
-
- stream
- << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">"
- << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
- << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
- << "<Media>Audio</Media>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::sessionTextConnectSendMessage(sessionState *session)
-{
- LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL;
-
- std::ostringstream stream;
-
- stream
- << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">"
- << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
- << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::sessionTerminate()
-{
- mSessionTerminateRequested = true;
-}
-
-void LLVoiceClient::requestRelog()
-{
- mSessionTerminateRequested = true;
- mRelogRequested = true;
-}
-
-
-void LLVoiceClient::leaveAudioSession()
-{
- if(mAudioSession)
+ else if(voice_server == "vivox")
{
- 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;
-
- default:
- LL_WARNS("Voice") << "called from unknown state" << LL_ENDL;
- break;
- }
+ mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance();
}
else
{
- LL_WARNS("Voice") << "called with no active session" << LL_ENDL;
- setState(stateSessionTerminated);
+ mVoiceModule = NULL;
+ return;
}
+ mVoiceModule->init(m_servicePump);
+ mVoiceModule->userAuthorized(user_id, agentID);
}
-void LLVoiceClient::sessionTerminateSendMessage(sessionState *session)
-{
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">"
- << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::sessionGroupTerminateSendMessage(sessionState *session)
-{
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">"
- << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session)
-{
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">"
- << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
- << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
- << "<Media>Audio</Media>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-
-}
-
-void LLVoiceClient::sessionTextDisconnectSendMessage(sessionState *session)
-{
- std::ostringstream stream;
-
- LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.TextDisconnect.1\">"
- << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
- << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::getCaptureDevicesSendMessage()
-{
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::getRenderDevicesSendMessage()
-{
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::clearCaptureDevices()
-{
- LL_DEBUGS("Voice") << "called" << LL_ENDL;
- mCaptureDevices.clear();
-}
-
-void LLVoiceClient::addCaptureDevice(const std::string& name)
-{
- LL_DEBUGS("Voice") << name << LL_ENDL;
-
- mCaptureDevices.push_back(name);
-}
-LLVoiceClient::deviceList *LLVoiceClient::getCaptureDevices()
+void LLVoiceClient::terminate()
{
- return &mCaptureDevices;
+ if (mVoiceModule) mVoiceModule->terminate();
+ mVoiceModule = NULL;
}
-void LLVoiceClient::setCaptureDevice(const std::string& name)
+const LLVoiceVersionInfo LLVoiceClient::getVersion()
{
- if(name == "Default")
+ if (mVoiceModule)
{
- if(!mCaptureDevice.empty())
- {
- mCaptureDevice.clear();
- mCaptureDeviceDirty = true;
- }
+ return mVoiceModule->getVersion();
}
else
{
- if(mCaptureDevice != name)
- {
- mCaptureDevice = name;
- mCaptureDeviceDirty = true;
- }
+ LLVoiceVersionInfo result;
+ result.serverVersion = std::string();
+ result.serverType = std::string();
+ return result;
}
}
-void LLVoiceClient::clearRenderDevices()
-{
- LL_DEBUGS("Voice") << "called" << LL_ENDL;
- mRenderDevices.clear();
-}
-
-void LLVoiceClient::addRenderDevice(const std::string& name)
-{
- LL_DEBUGS("Voice") << name << LL_ENDL;
- mRenderDevices.push_back(name);
-}
-
-LLVoiceClient::deviceList *LLVoiceClient::getRenderDevices()
+void LLVoiceClient::updateSettings()
{
- return &mRenderDevices;
+ if (mVoiceModule) mVoiceModule->updateSettings();
}
-void LLVoiceClient::setRenderDevice(const std::string& name)
-{
- if(name == "Default")
- {
- if(!mRenderDevice.empty())
- {
- mRenderDevice.clear();
- mRenderDeviceDirty = true;
- }
- }
- else
- {
- if(mRenderDevice != name)
- {
- mRenderDevice = name;
- mRenderDeviceDirty = true;
- }
- }
-
-}
+//--------------------------------------------------
+// tuning
void LLVoiceClient::tuningStart()
{
- mTuningMode = true;
- if(getState() >= stateNoChannel)
- {
- sessionTerminate();
- }
+ if (mVoiceModule) mVoiceModule->tuningStart();
}
void LLVoiceClient::tuningStop()
{
- mTuningMode = false;
+ if (mVoiceModule) mVoiceModule->tuningStop();
}
bool LLVoiceClient::inTuningMode()
{
- bool result = false;
- switch(getState())
- {
- case stateMicTuningRunning:
- result = true;
- break;
- default:
- break;
- }
- return result;
-}
-
-void LLVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop)
-{
- mTuningAudioFile = name;
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">"
- << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
- << "<Loop>" << (loop?"1":"0") << "</Loop>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::tuningRenderStopSendMessage()
-{
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
- << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::tuningCaptureStartSendMessage(int duration)
-{
- LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL;
-
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">"
- << "<Duration>" << duration << "</Duration>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-}
-
-void LLVoiceClient::tuningCaptureStopSendMessage()
-{
- LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL;
-
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
-
- mTuningEnergy = 0.0f;
-}
-
-void LLVoiceClient::tuningSetMicVolume(float volume)
-{
- int scaled_volume = scale_mic_volume(volume);
-
- if(scaled_volume != mTuningMicVolume)
- {
- mTuningMicVolume = scaled_volume;
- mTuningMicVolumeDirty = true;
- }
-}
-
-void LLVoiceClient::tuningSetSpeakerVolume(float volume)
-{
- int scaled_volume = scale_speaker_volume(volume);
-
- if(scaled_volume != mTuningSpeakerVolume)
- {
- mTuningSpeakerVolume = scaled_volume;
- mTuningSpeakerVolumeDirty = true;
- }
-}
-
-float LLVoiceClient::tuningGetEnergy(void)
-{
- return mTuningEnergy;
-}
-
-bool LLVoiceClient::deviceSettingsAvailable()
-{
- bool result = true;
-
- if(!mConnected)
- result = false;
-
- if(mRenderDevices.empty())
- result = false;
-
- return result;
-}
-
-void LLVoiceClient::refreshDeviceLists(bool clearCurrentList)
-{
- if(clearCurrentList)
- {
- clearCaptureDevices();
- clearRenderDevices();
- }
- getCaptureDevicesSendMessage();
- getRenderDevicesSendMessage();
-}
-
-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;
-
- // Try to relaunch the daemon
- setState(stateDisableCleanup);
-}
-
-void LLVoiceClient::giveUp()
-{
- // All has failed. Clean up and stop trying.
- closeSocket();
- deleteAllSessions();
- deleteAllBuddies();
-
- setState(stateJail);
-}
-
-static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel)
-{
- F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the new position and velocity
- F64 npos[3];
-
- // The original XML command was sent like this:
- /*
- << "<Position>"
- << "<X>" << pos[VX] << "</X>"
- << "<Y>" << pos[VZ] << "</Y>"
- << "<Z>" << pos[VY] << "</Z>"
- << "</Position>"
- << "<Velocity>"
- << "<X>" << mAvatarVelocity[VX] << "</X>"
- << "<Y>" << mAvatarVelocity[VZ] << "</Y>"
- << "<Z>" << mAvatarVelocity[VY] << "</Z>"
- << "</Velocity>"
- << "<AtOrientation>"
- << "<X>" << l.mV[VX] << "</X>"
- << "<Y>" << u.mV[VX] << "</Y>"
- << "<Z>" << a.mV[VX] << "</Z>"
- << "</AtOrientation>"
- << "<UpOrientation>"
- << "<X>" << l.mV[VZ] << "</X>"
- << "<Y>" << u.mV[VY] << "</Y>"
- << "<Z>" << a.mV[VZ] << "</Z>"
- << "</UpOrientation>"
- << "<LeftOrientation>"
- << "<X>" << l.mV [VY] << "</X>"
- << "<Y>" << u.mV [VZ] << "</Y>"
- << "<Z>" << a.mV [VY] << "</Z>"
- << "</LeftOrientation>";
- */
-
-#if 1
- // This was the original transform done when building the XML command
- nat[0] = left.mV[VX];
- nat[1] = up.mV[VX];
- nat[2] = at.mV[VX];
-
- nup[0] = left.mV[VZ];
- nup[1] = up.mV[VY];
- nup[2] = at.mV[VZ];
-
- nl[0] = left.mV[VY];
- nl[1] = up.mV[VZ];
- nl[2] = at.mV[VY];
-
- npos[0] = pos.mdV[VX];
- npos[1] = pos.mdV[VZ];
- npos[2] = pos.mdV[VY];
-
- nvel[0] = vel.mV[VX];
- nvel[1] = vel.mV[VZ];
- nvel[2] = vel.mV[VY];
-
- for(int i=0;i<3;++i) {
- at.mV[i] = nat[i];
- up.mV[i] = nup[i];
- left.mV[i] = nl[i];
- pos.mdV[i] = npos[i];
- }
-
- // This was the original transform done in the SDK
- nat[0] = at.mV[2];
- nat[1] = 0; // y component of at vector is always 0, this was up[2]
- nat[2] = -1 * left.mV[2];
-
- // We override whatever the application gives us
- nup[0] = 0; // x component of up vector is always 0
- nup[1] = 1; // y component of up vector is always 1
- nup[2] = 0; // z component of up vector is always 0
-
- nl[0] = at.mV[0];
- nl[1] = 0; // y component of left vector is always zero, this was up[0]
- nl[2] = -1 * left.mV[0];
-
- npos[2] = pos.mdV[2] * -1.0;
- npos[1] = pos.mdV[1];
- npos[0] = pos.mdV[0];
-
- for(int i=0;i<3;++i) {
- at.mV[i] = nat[i];
- up.mV[i] = nup[i];
- left.mV[i] = nl[i];
- pos.mdV[i] = npos[i];
- }
-#else
- // This is the compose of the two transforms (at least, that's what I'm trying for)
- nat[0] = at.mV[VX];
- nat[1] = 0; // y component of at vector is always 0, this was up[2]
- nat[2] = -1 * up.mV[VZ];
-
- // We override whatever the application gives us
- nup[0] = 0; // x component of up vector is always 0
- nup[1] = 1; // y component of up vector is always 1
- nup[2] = 0; // z component of up vector is always 0
-
- nl[0] = left.mV[VX];
- nl[1] = 0; // y component of left vector is always zero, this was up[0]
- nl[2] = -1 * left.mV[VY];
-
- npos[0] = pos.mdV[VX];
- npos[1] = pos.mdV[VZ];
- npos[2] = pos.mdV[VY] * -1.0;
-
- nvel[0] = vel.mV[VX];
- nvel[1] = vel.mV[VZ];
- nvel[2] = vel.mV[VY];
-
- for(int i=0;i<3;++i) {
- at.mV[i] = nat[i];
- up.mV[i] = nup[i];
- left.mV[i] = nl[i];
- pos.mdV[i] = npos[i];
- }
-
-#endif
-}
-
-void LLVoiceClient::sendPositionalUpdate(void)
-{
- std::ostringstream stream;
-
- if(mSpatialCoordsDirty)
- {
- LLVector3 l, u, a, vel;
- LLVector3d pos;
-
- mSpatialCoordsDirty = false;
-
- // Always send both speaker and listener positions together.
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">"
- << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>";
-
- stream << "<SpeakerPosition>";
-
-// 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
- << "<Position>"
- << "<X>" << pos.mdV[VX] << "</X>"
- << "<Y>" << pos.mdV[VY] << "</Y>"
- << "<Z>" << pos.mdV[VZ] << "</Z>"
- << "</Position>"
- << "<Velocity>"
- << "<X>" << vel.mV[VX] << "</X>"
- << "<Y>" << vel.mV[VY] << "</Y>"
- << "<Z>" << vel.mV[VZ] << "</Z>"
- << "</Velocity>"
- << "<AtOrientation>"
- << "<X>" << a.mV[VX] << "</X>"
- << "<Y>" << a.mV[VY] << "</Y>"
- << "<Z>" << a.mV[VZ] << "</Z>"
- << "</AtOrientation>"
- << "<UpOrientation>"
- << "<X>" << u.mV[VX] << "</X>"
- << "<Y>" << u.mV[VY] << "</Y>"
- << "<Z>" << u.mV[VZ] << "</Z>"
- << "</UpOrientation>"
- << "<LeftOrientation>"
- << "<X>" << l.mV [VX] << "</X>"
- << "<Y>" << l.mV [VY] << "</Y>"
- << "<Z>" << l.mV [VZ] << "</Z>"
- << "</LeftOrientation>";
-
- stream << "</SpeakerPosition>";
-
- stream << "<ListenerPosition>";
-
- LLVector3d earPosition;
- LLVector3 earVelocity;
- LLMatrix3 earRot;
-
- switch(mEarLocation)
- {
- case earLocCamera:
- default:
- earPosition = mCameraPosition;
- earVelocity = mCameraVelocity;
- earRot = mCameraRot;
- break;
-
- case earLocAvatar:
- earPosition = mAvatarPosition;
- earVelocity = mAvatarVelocity;
- earRot = mAvatarRot;
- break;
-
- case earLocMixed:
- earPosition = mAvatarPosition;
- earVelocity = mAvatarVelocity;
- earRot = mCameraRot;
- break;
- }
-
- l = earRot.getLeftRow();
- u = earRot.getUpRow();
- a = earRot.getFwdRow();
- pos = earPosition;
- vel = earVelocity;
-
-// LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;
-
- oldSDKTransform(l, u, a, pos, vel);
-
- stream
- << "<Position>"
- << "<X>" << pos.mdV[VX] << "</X>"
- << "<Y>" << pos.mdV[VY] << "</Y>"
- << "<Z>" << pos.mdV[VZ] << "</Z>"
- << "</Position>"
- << "<Velocity>"
- << "<X>" << vel.mV[VX] << "</X>"
- << "<Y>" << vel.mV[VY] << "</Y>"
- << "<Z>" << vel.mV[VZ] << "</Z>"
- << "</Velocity>"
- << "<AtOrientation>"
- << "<X>" << a.mV[VX] << "</X>"
- << "<Y>" << a.mV[VY] << "</Y>"
- << "<Z>" << a.mV[VZ] << "</Z>"
- << "</AtOrientation>"
- << "<UpOrientation>"
- << "<X>" << u.mV[VX] << "</X>"
- << "<Y>" << u.mV[VY] << "</Y>"
- << "<Z>" << u.mV[VZ] << "</Z>"
- << "</UpOrientation>"
- << "<LeftOrientation>"
- << "<X>" << l.mV [VX] << "</X>"
- << "<Y>" << l.mV [VY] << "</Y>"
- << "<Z>" << l.mV [VZ] << "</Z>"
- << "</LeftOrientation>";
-
-
- stream << "</ListenerPosition>";
-
- stream << "</Request>\n\n\n";
- }
-
- if(mAudioSession && mAudioSession->mVolumeDirty)
- {
- participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
-
- mAudioSession->mVolumeDirty = false;
-
- for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
- {
- participantState *p = iter->second;
-
- if(p->mVolumeDirty)
- {
- // Can't set volume/mute for yourself
- if(!p->mIsSelf)
- {
- int volume = 56; // nominal default value
- bool mute = p->mOnMuteList;
-
- if(p->mUserVolume != -1)
- {
- // scale from user volume in the range 0-400 (with 100 as "normal") to vivox volume in the range 0-100 (with 56 as "normal")
- if(p->mUserVolume < 100)
- volume = (p->mUserVolume * 56) / 100;
- else
- volume = (((p->mUserVolume - 100) * (100 - 56)) / 300) + 56;
- }
- else if(p->mVolume != -1)
- {
- // Use the previously reported internal volume (comes in with a ParticipantUpdatedEvent)
- volume = p->mVolume;
- }
-
-
- if(mute)
- {
- // SetParticipantMuteForMe doesn't work in p2p sessions.
- // If we want the user to be muted, set their volume to 0 as well.
- // This isn't perfect, but it will at least reduce their volume to a minimum.
- volume = 0;
- }
-
- if(volume == 0)
- mute = true;
-
- LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL;
-
- // SLIM SDK: Send both volume and mute commands.
-
- // Send a "volume for me" command for the user.
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">"
- << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
- << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
- << "<Volume>" << volume << "</Volume>"
- << "</Request>\n\n\n";
-
- // Send a "mute for me" command for the user
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">"
- << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
- << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
- << "<Mute>" << (mute?"1":"0") << "</Mute>"
- << "</Request>\n\n\n";
- }
-
- p->mVolumeDirty = false;
- }
- }
- }
-
- buildLocalAudioUpdates(stream);
-
- if(!stream.str().empty())
+ if (mVoiceModule)
{
- writeString(stream.str());
+ return mVoiceModule->inTuningMode();
}
-
- // 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)
+ else
{
- LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">"
- << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>"
- << "</Request>"
- << "\n\n\n";
-
- mCaptureDeviceDirty = false;
+ return false;
}
}
-void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
+void LLVoiceClient::tuningSetMicVolume(float volume)
{
- if(mRenderDeviceDirty)
- {
- LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
- << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
- << "</Request>"
- << "\n\n\n";
- mRenderDeviceDirty = false;
- }
+ if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume);
}
-void LLVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream)
+void LLVoiceClient::tuningSetSpeakerVolume(float volume)
{
- 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)
-
- LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL;
-
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
- << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
- << "<Value>" << (mPTT?"false":"true") << "</Value>"
- << "</Request>\n\n\n";
-
- }
-
- if(mSpeakerMuteDirty)
- {
- const char *muteval = ((mSpeakerVolume == 0)?"true":"false");
-
- mSpeakerMuteDirty = false;
-
- LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL;
-
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">"
- << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
- << "<Value>" << muteval << "</Value>"
- << "</Request>\n\n\n";
-
- }
-
- if(mSpeakerVolumeDirty)
- {
- mSpeakerVolumeDirty = false;
-
- LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL;
-
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">"
- << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
- << "<Value>" << mSpeakerVolume << "</Value>"
- << "</Request>\n\n\n";
-
- }
-
- if(mMicVolumeDirty)
- {
- mMicVolumeDirty = false;
-
- LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL;
-
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">"
- << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
- << "<Value>" << mMicVolume << "</Value>"
- << "</Request>\n\n\n";
- }
-
-
+ if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume);
}
-void LLVoiceClient::checkFriend(const LLUUID& id)
+float LLVoiceClient::tuningGetEnergy(void)
{
- std::string name;
- buddyListEntry *buddy = findBuddy(id);
-
- // Make sure we don't add a name before it's been looked up.
- if(gCacheName->getFullName(id, name))
+ if (mVoiceModule)
{
-
- const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id);
- bool canSeeMeOnline = false;
- if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS))
- canSeeMeOnline = true;
-
- // When we get here, mNeedsSend is true and mInSLFriends is false. Change them as necessary.
-
- if(buddy)
- {
- // This buddy is already in both lists.
-
- if(name != buddy->mDisplayName)
- {
- // The buddy is in the list with the wrong name. Update it with the correct name.
- LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL;
- buddy->mDisplayName = name;
- buddy->mNeedsNameUpdate = true; // This will cause the buddy to be resent.
- }
- }
- else
- {
- // This buddy was not in the vivox list, needs to be added.
- buddy = addBuddy(sipURIFromID(id), name);
- buddy->mUUID = id;
- }
-
- // In all the above cases, the buddy is in the SL friends list (which is how we got here).
- buddy->mInSLFriends = true;
- buddy->mCanSeeMeOnline = canSeeMeOnline;
- buddy->mNameResolved = true;
-
+ return mVoiceModule->tuningGetEnergy();
}
else
{
- // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has.
- if(buddy)
- {
- buddy->mNameResolved = false;
- }
-
- // Initiate a lookup.
- // The "lookup completed" callback will ensure that the friends list is rechecked after it completes.
- lookupName(id);
+ return 0.0;
}
}
-void LLVoiceClient::clearAllLists()
-{
- // FOR TESTING ONLY
-
- // This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about.
- buddyListMap::iterator buddy_it;
- for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
- {
- buddyListEntry *buddy = buddy_it->second;
- buddy_it++;
-
- std::ostringstream stream;
-
- if(buddy->mInVivoxBuddies)
- {
- // delete this entry from the vivox buddy list
- buddy->mInVivoxBuddies = false;
- LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
- << "</Request>\n\n\n";
- }
-
- if(buddy->mHasBlockListEntry)
- {
- // Delete the associated block list entry (so the block list doesn't fill up with junk)
- buddy->mHasBlockListEntry = false;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BlockMask>" << buddy->mURI << "</BlockMask>"
- << "</Request>\n\n\n";
- }
- if(buddy->mHasAutoAcceptListEntry)
- {
- // Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk)
- buddy->mHasAutoAcceptListEntry = false;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
- << "</Request>\n\n\n";
- }
-
- writeString(stream.str());
- }
-}
+//------------------------------------------------
+// devices
-void LLVoiceClient::sendFriendsListUpdates()
+bool LLVoiceClient::deviceSettingsAvailable()
{
- if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty)
- {
- mFriendsListDirty = false;
-
- if(0)
- {
- // FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries.
- clearAllLists();
- return;
- }
-
- LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL;
-
- buddyListMap::iterator buddy_it;
- for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
- {
- // reset the temp flags in the local buddy list
- buddy_it->second->mInSLFriends = false;
- }
-
- // correlate with the friends list
- {
- LLCollectAllBuddies collect;
- LLAvatarTracker::instance().applyFunctor(collect);
- LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin();
- LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end();
-
- for ( ; it != end; ++it)
- {
- checkFriend(it->second);
- }
- it = collect.mOffline.begin();
- end = collect.mOffline.end();
- for ( ; it != end; ++it)
- {
- checkFriend(it->second);
- }
- }
-
- LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL;
-
- for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
- {
- buddyListEntry *buddy = buddy_it->second;
- buddy_it++;
-
- // Ignore entries that aren't resolved yet.
- if(buddy->mNameResolved)
- {
- std::ostringstream stream;
-
- if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate))
- {
- if(mNumberOfAliases > 0)
- {
- // Add (or update) this entry in the vivox buddy list
- buddy->mInVivoxBuddies = true;
- buddy->mNeedsNameUpdate = false;
- LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddySet.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
- << "<DisplayName>" << buddy->mDisplayName << "</DisplayName>"
- << "<BuddyData></BuddyData>" // Without this, SLVoice doesn't seem to parse the command.
- << "<GroupID>0</GroupID>"
- << "</Request>\n\n\n";
- }
- }
- else if(!buddy->mInSLFriends)
- {
- // This entry no longer exists in your SL friends list. Remove all traces of it from the Vivox buddy list.
- if(buddy->mInVivoxBuddies)
- {
- // delete this entry from the vivox buddy list
- buddy->mInVivoxBuddies = false;
- LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
- << "</Request>\n\n\n";
- }
-
- if(buddy->mHasBlockListEntry)
- {
- // Delete the associated block list entry, if any
- buddy->mHasBlockListEntry = false;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BlockMask>" << buddy->mURI << "</BlockMask>"
- << "</Request>\n\n\n";
- }
- if(buddy->mHasAutoAcceptListEntry)
- {
- // Delete the associated auto-accept list entry, if any
- buddy->mHasAutoAcceptListEntry = false;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
- << "</Request>\n\n\n";
- }
- }
-
- if(buddy->mInSLFriends)
- {
-
- if(buddy->mCanSeeMeOnline)
- {
- // Buddy should not be blocked.
-
- // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
-
- // If the buddy has a block list entry, delete it.
- if(buddy->mHasBlockListEntry)
- {
- buddy->mHasBlockListEntry = false;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BlockMask>" << buddy->mURI << "</BlockMask>"
- << "</Request>\n\n\n";
-
-
- // If we just deleted a block list entry, add an auto-accept entry.
- if(!buddy->mHasAutoAcceptListEntry)
- {
- buddy->mHasAutoAcceptListEntry = true;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateAutoAcceptRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
- << "<AutoAddAsBuddy>0</AutoAddAsBuddy>"
- << "</Request>\n\n\n";
- }
- }
- }
- else
- {
- // Buddy should be blocked.
-
- // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
-
- // If this buddy has an autoaccept entry, delete it
- if(buddy->mHasAutoAcceptListEntry)
- {
- buddy->mHasAutoAcceptListEntry = false;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
- << "</Request>\n\n\n";
-
- // If we just deleted an auto-accept entry, add a block list entry.
- if(!buddy->mHasBlockListEntry)
- {
- buddy->mHasBlockListEntry = true;
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateBlockRule.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BlockMask>" << buddy->mURI << "</BlockMask>"
- << "<PresenceOnly>1</PresenceOnly>"
- << "</Request>\n\n\n";
- }
- }
- }
-
- if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies)
- {
- // Delete this entry from the local buddy list. This should NOT invalidate the iterator,
- // since it has already been incremented to the next entry.
- deleteBuddy(buddy->mURI);
- }
-
- }
- writeString(stream.str());
- }
- }
- }
-}
-
-/////////////////////////////
-// Response/Event handlers
-
-void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID)
-{
- if(statusCode != 0)
- {
- 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;
- mAPIVersion = versionID;
- 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)
-// {
-// setState(stateLoggedIn);
-// }
- }
-}
-
-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)
- {
- session->mErrorStatusCode = statusCode;
- session->mErrorStatusString = statusString;
- if(session == mAudioSession)
- {
- setState(stateJoinSessionFailed);
- }
- else
- {
- reapSession(session);
- }
- }
- }
- else
- {
- LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
- if(session)
- {
- setSessionHandle(session, sessionHandle);
- }
- }
-}
-
-void LLVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
-{
- sessionState *session = findSessionBeingCreatedByURI(requestId);
-
- if(session)
+ if (mVoiceModule)
{
- 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);
- }
- }
+ return mVoiceModule->deviceSettingsAvailable();
}
else
{
- LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL;
- if(session)
- {
- setSessionHandle(session, sessionHandle);
- }
+ return false;
}
}
-void LLVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString)
+void LLVoiceClient::refreshDeviceLists(bool clearCurrentList)
{
- sessionState *session = findSession(requestId);
- if(statusCode != 0)
- {
- LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
- if(session)
- {
- 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)
-{
- if(statusCode != 0)
- {
- LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL;
- // Should this ever fail? do we care if it does?
- }
+ if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList);
}
-void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
+void LLVoiceClient::setCaptureDevice(const std::string& name)
{
- if(statusCode != 0)
- {
- LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL;
- // Should this ever fail? do we care if it does?
- }
+ if (mVoiceModule) mVoiceModule->setCaptureDevice(name);
- mConnected = false;
-
- if(getState() == stateConnectorStopping)
- {
- setState(stateConnectorStopped);
- }
}
-void LLVoiceClient::sessionAddedEvent(
- std::string &uriString,
- std::string &alias,
- std::string &sessionHandle,
- std::string &sessionGroupHandle,
- bool isChannel,
- bool incoming,
- std::string &nameString,
- std::string &applicationString)
+void LLVoiceClient::setRenderDevice(const std::string& name)
{
- sessionState *session = NULL;
-
- 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))
- {
- // Normal URI(base64-encoded UUID)
- }
- else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID))
- {
- // 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_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL;
- setUUIDFromStringHash(session->mCallerID, session->mSIPURI);
- session->mSynthesizedCallerID = true;
-
- // Can't look up the name in this case -- we have to extract it from the URI.
- std::string namePortion = nameFromsipURI(session->mSIPURI);
- if(namePortion.empty())
- {
- // Didn't seem to be a SIP URI, just use the whole provided name.
- namePortion = nameString;
- }
-
- // Some incoming names may be separated with an underscore instead of a space. Fix this.
- LLStringUtil::replaceChar(namePortion, '_', ' ');
-
- // Act like we just finished resolving the name (this stores it in all the right places)
- avatarNameResolved(session->mCallerID, namePortion);
- }
-
- LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL;
-
- if(!session->mSynthesizedCallerID)
- {
- // If we got here, we don't have a proper name. Initiate a lookup.
- lookupName(session->mCallerID);
- }
- }
- }
+ if (mVoiceModule) mVoiceModule->setRenderDevice(name);
}
-void LLVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle)
+const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices()
{
- LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL;
-
-#if USE_SESSION_GROUPS
- if(mMainSessionGroupHandle.empty())
+ static LLVoiceDeviceList nullCaptureDevices;
+ if (mVoiceModule)
{
- // This is the first (i.e. "main") session group. Save its handle.
- mMainSessionGroupHandle = sessionGroupHandle;
+ return mVoiceModule->getCaptureDevices();
}
else
{
- LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL;
- }
-#endif
-}
-
-void LLVoiceClient::joinedAudioSession(sessionState *session)
-{
- if(mAudioSession != session)
- {
- sessionState *oldSession = mAudioSession;
-
- mAudioSession = session;
- mAudioSessionChanged = true;
-
- // The old session may now need to be deleted.
- reapSession(oldSession);
- }
-
- // This is the session we're joining.
- if(getState() == stateJoiningSession)
- {
- setState(stateSessionJoined);
-
- // SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now.
- // Add the current user as a participant here.
- participantState *participant = session->addParticipant(sipURIFromName(mAccountName));
- if(participant)
- {
- participant->mIsSelf = true;
- lookupName(participant->mAvatarID);
-
- 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)
- {
- lookupName(participant->mAvatarID);
- }
- else if(!session->mName.empty())
- {
- 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;
- }
- }
+ return nullCaptureDevices;
}
}
-void LLVoiceClient::sessionRemovedEvent(
- std::string &sessionHandle,
- std::string &sessionGroupHandle)
-{
- LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL;
-
- sessionState *session = findSession(sessionHandle);
- if(session)
- {
- leftAudioSession(session);
-
- // This message invalidates the session's handle. Set it to empty.
- setSessionHandle(session);
-
- // This also means that the session's session group is now empty.
- // Terminate the session group so it doesn't leak.
- sessionGroupTerminateSendMessage(session);
-
- // Reset the media state (we now have no info)
- session->mMediaStreamState = streamStateUnknown;
- session->mTextStreamState = streamStateUnknown;
-
- // Conditionally delete the session
- reapSession(session);
- }
- else
- {
- LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL;
- }
-}
-void LLVoiceClient::reapSession(sessionState *session)
+const LLVoiceDeviceList& LLVoiceClient::getRenderDevices()
{
- if(session)
+ static LLVoiceDeviceList nullRenderDevices;
+ if (mVoiceModule)
{
- if(!session->mHandle.empty())
- {
- LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL;
- }
- else if(session->mCreateInProgress)
- {
- LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL;
- }
- else if(session->mMediaConnectInProgress)
- {
- LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL;
- }
- else if(session == mAudioSession)
- {
- LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL;
- }
- else if(session == mNextAudioSession)
- {
- LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL;
- }
- else
- {
- // TODO: Question: Should we check for queued text messages here?
- // We don't have a reason to keep tracking this session, so just delete it.
- LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL;
- deleteSession(session);
- session = NULL;
- }
+ return mVoiceModule->getRenderDevices();
}
else
{
-// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL;
- }
-}
-
-// Returns true if the session seems to indicate we've moved to a region on a different voice server
-bool LLVoiceClient::sessionNeedsRelog(sessionState *session)
-{
- bool result = false;
-
- if(session != NULL)
- {
- // Only make this check for spatial channels (so it won't happen for group or p2p calls)
- if(session->mIsSpatial)
- {
- std::string::size_type atsign;
-
- atsign = session->mSIPURI.find("@");
-
- if(atsign != std::string::npos)
- {
- std::string urihost = session->mSIPURI.substr(atsign + 1);
- if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str()))
- {
- // 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;
-}
-
-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;
- }
+ return nullRenderDevices;
}
}
-void LLVoiceClient::accountLoginStateChangeEvent(
- std::string &accountHandle,
- int statusCode,
- std::string &statusString,
- int state)
-{
- 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
- */
-
- switch(state)
- {
- case 1:
- if(getState() == stateLoggingIn)
- {
- setState(stateLoggedIn);
- }
- break;
-
- case 3:
- // The user is in the process of logging out.
- setState(stateLoggingOut);
- break;
- case 0:
- // The user has been logged out.
- setState(stateLoggedOut);
- break;
-
- default:
- //Used to be a commented out warning
- LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL;
- break;
- }
-}
+//--------------------------------------------------
+// participants
-void LLVoiceClient::mediaStreamUpdatedEvent(
- std::string &sessionHandle,
- std::string &sessionGroupHandle,
- int statusCode,
- std::string &statusString,
- int state,
- bool incoming)
+std::vector<LLUUID> LLVoiceClient::getParticipantList(void)
{
- sessionState *session = findSession(sessionHandle);
-
- LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL;
-
- if(session)
+ if (mVoiceModule)
{
- // We know about this session
-
- // Save the state for later use
- session->mMediaStreamState = state;
-
- switch(statusCode)
- {
- case 0:
- case 200:
- // generic success
- // Don't change the saved error code (it may have been set elsewhere)
- break;
- default:
- // save the status code for later
- session->mErrorStatusCode = statusCode;
- break;
- }
-
- switch(state)
- {
- case streamStateIdle:
- // Standard "left audio session"
- session->mVoiceEnabled = false;
- session->mMediaConnectInProgress = false;
- leftAudioSession(session);
- break;
-
- case streamStateConnected:
- session->mVoiceEnabled = true;
- session->mMediaConnectInProgress = false;
- joinedAudioSession(session);
- break;
-
- case streamStateRinging:
- if(incoming)
- {
- // Send the voice chat invite to the GUI layer
- // *TODO: Question: Should we correlate with the mute list here?
- session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID);
- session->mVoiceInvitePending = true;
- if(session->mName.empty())
- {
- lookupName(session->mCallerID);
- }
- else
- {
- // Act like we just finished resolving the name
- avatarNameResolved(session->mCallerID, session->mName);
- }
- }
- break;
-
- default:
- LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
- break;
-
- }
-
+ return mVoiceModule->getParticipantList();
}
else
{
- LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL;
- }
-}
-
-void LLVoiceClient::textStreamUpdatedEvent(
- std::string &sessionHandle,
- std::string &sessionGroupHandle,
- bool enabled,
- int state,
- bool incoming)
-{
- sessionState *session = findSession(sessionHandle);
-
- if(session)
- {
- // Save the state for later use
- session->mTextStreamState = state;
-
- // We know about this session
- switch(state)
- {
- case 0: // We see this when the text stream closes
- LL_DEBUGS("Voice") << "stream closed" << LL_ENDL;
- break;
-
- case 1: // We see this on an incoming call from the Connector
- // Try to send any text messages queued for this session.
- sendQueuedTextMessages(session);
-
- // Send the text chat invite to the GUI layer
- // TODO: Question: Should we correlate with the mute list here?
- session->mTextInvitePending = true;
- if(session->mName.empty())
- {
- 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::participantAddedEvent(
- std::string &sessionHandle,
- std::string &sessionGroupHandle,
- std::string &uriString,
- std::string &alias,
- std::string &nameString,
- std::string &displayNameString,
- int participantType)
-{
- sessionState *session = findSession(sessionHandle);
- if(session)
- {
- participantState *participant = session->addParticipant(uriString);
- if(participant)
- {
- participant->mAccountName = nameString;
-
- LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName
- << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
-
- 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);
- }
- }
+ return std::vector<LLUUID>();
}
}
-void LLVoiceClient::participantRemovedEvent(
- std::string &sessionHandle,
- std::string &sessionGroupHandle,
- std::string &uriString,
- std::string &alias,
- std::string &nameString)
-{
- sessionState *session = findSession(sessionHandle);
- if(session)
- {
- participantState *participant = session->findParticipant(uriString);
- if(participant)
- {
- session->removeParticipant(participant);
- }
- else
- {
- LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL;
- }
- }
- else
- {
- LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
- }
-}
+//--------------------------------------------------
+// text chat
-void LLVoiceClient::participantUpdatedEvent(
- std::string &sessionHandle,
- std::string &sessionGroupHandle,
- std::string &uriString,
- std::string &alias,
- bool isModeratorMuted,
- bool isSpeaking,
- int volume,
- F32 energy)
+BOOL LLVoiceClient::isSessionTextIMPossible(const LLUUID& id)
{
- sessionState *session = findSession(sessionHandle);
- if(session)
+ if (mVoiceModule)
{
- participantState *participant = session->findParticipant(uriString);
-
- if(participant)
- {
- 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;
-
-
- // *HACK: mantipov: added while working on EXT-3544
- /*
- Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE
- LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER.
-
- participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted
- Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug.
- Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates.
-
- But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post()
- voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager
- and event is not fired.
-
- So, we have to call LLSpeakerMgr::update() here. In any case it is better than call it
- in LLCallFloater::draw()
- */
- LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel();
-
- // ignore session ID of local chat
- if (voice_cnl && voice_cnl->getSessionID().notNull())
- {
- LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID());
- if (speaker_manager)
- {
- speaker_manager->update(true);
- }
- }
- }
- else
- {
- LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
- }
+ return mVoiceModule->isSessionTextIMPossible(id);
}
else
{
- LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
- }
+ return FALSE;
+ }
}
-void LLVoiceClient::buddyPresenceEvent(
- std::string &uriString,
- std::string &alias,
- std::string &statusString,
- std::string &applicationString)
+BOOL LLVoiceClient::isSessionCallBackPossible(const LLUUID& id)
{
- buddyListEntry *buddy = findBuddy(uriString);
-
- if(buddy)
+ if (mVoiceModule)
{
- LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL;
- LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
-
- if(applicationString.empty())
- {
- // This presence event is from a client that doesn't set up the Application string. Do things the old-skool way.
- // NOTE: this will be needed to support people who aren't on the 3010-class SDK yet.
-
- if ( stricmp("Unknown", statusString.c_str())== 0)
- {
- // User went offline with a non-SLim-enabled viewer.
- buddy->mOnlineSL = false;
- }
- else if ( stricmp("Online", statusString.c_str())== 0)
- {
- // User came online with a non-SLim-enabled viewer.
- buddy->mOnlineSL = true;
- }
- else
- {
- // If the user is online through SLim, their status will be "Online-slc", "Away", or something else.
- // NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string.
- buddy->mOnlineSLim = true;
- }
- }
- else if(applicationString.find("SecondLifeViewer") != std::string::npos)
- {
- // This presence event is from a viewer that sets the application string
- if ( stricmp("Unknown", statusString.c_str())== 0)
- {
- // Viewer says they're offline
- buddy->mOnlineSL = false;
- }
- else
- {
- // Viewer says they're online
- buddy->mOnlineSL = true;
- }
- }
- else
- {
- // This presence event is from something which is NOT the SL viewer (assume it's SLim).
- if ( stricmp("Unknown", statusString.c_str())== 0)
- {
- // SLim says they're offline
- buddy->mOnlineSLim = false;
- }
- else
- {
- // SLim says they're online
- buddy->mOnlineSLim = true;
- }
- }
-
- LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
-
- // HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change.
- LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID));
-
- notifyFriendObservers();
+ return mVoiceModule->isSessionCallBackPossible(id);
}
else
{
- LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL;
+ return FALSE;
}
}
-void LLVoiceClient::messageEvent(
- std::string &sessionHandle,
- std::string &uriString,
- std::string &alias,
- std::string &messageHeader,
- std::string &messageBody,
- std::string &applicationString)
-{
- LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL;
-// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL;
-
- if(messageHeader.find("text/html") != std::string::npos)
- {
- std::string message;
-
- {
- const std::string startMarker = "<body";
- const std::string startMarker2 = ">";
- const std::string endMarker = "</body>";
- const std::string startSpan = "<span";
- const std::string endSpan = "</span>";
- std::string::size_type start;
- std::string::size_type end;
-
- // Default to displaying the raw string, so the message gets through.
- message = messageBody;
-
- // Find the actual message text within the XML fragment
- start = messageBody.find(startMarker);
- start = messageBody.find(startMarker2, start);
- end = messageBody.find(endMarker);
-
- if(start != std::string::npos)
- {
- start += startMarker2.size();
-
- if(end != std::string::npos)
- end -= start;
-
- message.assign(messageBody, start, end);
- }
- else
- {
- // Didn't find a <body>, try looking for a <span> instead.
- start = messageBody.find(startSpan);
- start = messageBody.find(startMarker2, start);
- end = messageBody.find(endSpan);
-
- if(start != std::string::npos)
- {
- start += startMarker2.size();
-
- if(end != std::string::npos)
- end -= start;
-
- message.assign(messageBody, start, end);
- }
- }
- }
-
-// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL;
-
- // strip formatting tags
- {
- std::string::size_type start;
- std::string::size_type end;
-
- while((start = message.find('<')) != std::string::npos)
- {
- if((end = message.find('>', start + 1)) != std::string::npos)
- {
- // Strip out the tag
- message.erase(start, (end + 1) - start);
- }
- else
- {
- // Avoid an infinite loop
- break;
- }
- }
- }
-
- // Decode ampersand-escaped chars
- {
- std::string::size_type mark = 0;
-
- // The text may contain text encoded with &lt;, &gt;, and &amp;
- mark = 0;
- while((mark = message.find("&lt;", mark)) != std::string::npos)
- {
- message.replace(mark, 4, "<");
- mark += 1;
- }
-
- mark = 0;
- while((mark = message.find("&gt;", mark)) != std::string::npos)
- {
- message.replace(mark, 4, ">");
- mark += 1;
- }
-
- mark = 0;
- while((mark = message.find("&amp;", mark)) != std::string::npos)
- {
- message.replace(mark, 5, "&");
- mark += 1;
- }
- }
-
- // strip leading/trailing whitespace (since we always seem to get a couple newlines)
- LLStringUtil::trim(message);
-
-// LL_DEBUGS("Voice") << " stripped message = \n" << message << 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?
- }
-
- 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(),
- message.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(": ") + message;
- // If the chat should come in quietly (i.e. we're in busy mode), pretend it's from a local agent.
- LLFloaterChat::addChat( chat, TRUE, quiet_chat );
- }
- }
- }
-}
-
-void LLVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string &notificationType)
+BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message)
{
- sessionState *session = findSession(sessionHandle);
-
- if(session)
+ if (mVoiceModule)
{
- 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;
- }
+ return mVoiceModule->sendTextMessage(participant_id, message);
}
else
{
- LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL;
- }
-}
-
-void LLVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType)
-{
- buddyListEntry *buddy = findBuddy(buddyURI);
-
- if(!buddy)
- {
- // Couldn't find buddy by URI, try converting the alias...
- if(!alias.empty())
- {
- LLUUID id;
- if(IDFromName(alias, id))
- {
- buddy = findBuddy(id);
- }
- }
- }
-
- if(buddy)
- {
- std::ostringstream stream;
-
- if(buddy->mCanSeeMeOnline)
- {
- // Sending the response will create an auto-accept rule
- buddy->mHasAutoAcceptListEntry = true;
- }
- else
- {
- // Sending the response will create a block rule
- buddy->mHasBlockListEntry = true;
- }
-
- if(buddy->mInSLFriends)
- {
- buddy->mInVivoxBuddies = true;
- }
-
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.SendSubscriptionReply.1\">"
- << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
- << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
- << "<RuleType>" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "</RuleType>"
- << "<AutoAccept>"<< (buddy->mInSLFriends?"1":"0")<< "</AutoAccept>"
- << "<SubscriptionHandle>" << subscriptionHandle << "</SubscriptionHandle>"
- << "</Request>"
- << "\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
-{
- LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
- mTuningEnergy = energy;
-}
-
-void LLVoiceClient::buddyListChanged()
-{
- // This is called after we receive a BuddyAndGroupListChangedEvent.
- mBuddyListMapPopulated = true;
- mFriendsListDirty = true;
-}
-
-void LLVoiceClient::muteListChanged()
-{
- // The user's mute list has been updated. Go through the current participant list and sync it with the mute list.
- 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(-1),
- mOnMuteList(false),
- mUserVolume(-1),
- 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;
-}
-
-bool LLVoiceClient::participantState::isAvatar()
-{
- return mAvatarIDValid;
-}
-
-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;
-
- 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 != 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;
+ return FALSE;
+ }
}
-
-void LLVoiceClient::parcelChanged()
+void LLVoiceClient::endUserIMSession(const LLUUID& participant_id)
{
- if(getState() >= stateNoChannel)
- {
- // If the user is logged in, start a channel lookup.
- LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL;
-
- std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest");
- LLSD data;
- LLHTTPClient::post(
- url,
- data,
- new LLVoiceClientCapResponder);
- }
- else
+ if (mVoiceModule)
{
- // The transition to stateNoChannel needs to kick this off again.
- LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL;
+ mVoiceModule->endUserIMSession(participant_id);
}
}
-void LLVoiceClient::switchChannel(
- std::string uri,
- bool spatial,
- bool no_reconnect,
- bool is_p2p,
- std::string hash)
-{
- bool needsSwitch = false;
-
- LL_DEBUGS("Voice")
- << "called in state " << state2string(getState())
- << " with uri \"" << uri << "\""
- << (spatial?", spatial is true":", spatial is false")
- << LL_ENDL;
-
- switch(getState())
- {
- case stateJoinSessionFailed:
- case stateJoinSessionFailedWaiting:
- case stateNoChannel:
- // 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(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(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)
- {
- 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)
- {
- // We're already set up to join a channel, just needed to fill in the session URI
- }
- else
- {
- // State machine will come around and rejoin if uri/handle is not empty.
- sessionTerminate();
- }
- }
-}
+//----------------------------------------------
+// channels
-void LLVoiceClient::joinSession(sessionState *session)
+bool LLVoiceClient::inProximalChannel()
{
- mNextAudioSession = session;
-
- if(getState() <= stateNoChannel)
+ if (mVoiceModule)
{
- // We're already set up to join a channel, just needed to fill in the session handle
+ return mVoiceModule->inProximalChannel();
}
else
{
- // State machine will come around and rejoin if uri/handle is not empty.
- sessionTerminate();
+ return false;
}
}
@@ -5192,1831 +332,408 @@ void LLVoiceClient::setNonSpatialChannel(
const std::string &uri,
const std::string &credentials)
{
- switchChannel(uri, false, false, false, credentials);
+ if (mVoiceModule) mVoiceModule->setNonSpatialChannel(uri, 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);
+ if (mVoiceModule) mVoiceModule->setSpatialChannel(uri, credentials);
}
-LLVoiceClient::sessionState* LLVoiceClient::startUserIMSession(const LLUUID &uuid)
+void LLVoiceClient::leaveNonSpatialChannel()
{
- // 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;
+ if (mVoiceModule) mVoiceModule->leaveNonSpatialChannel();
}
-bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message)
+void LLVoiceClient::leaveChannel(void)
{
- 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;
+ if (mVoiceModule) mVoiceModule->leaveChannel();
}
-void LLVoiceClient::sendQueuedTextMessages(sessionState *session)
+std::string LLVoiceClient::getCurrentChannel()
{
- if(session->mTextStreamState == 1)
- {
- if(!session->mTextMsgQueue.empty())
- {
- std::ostringstream stream;
-
- while(!session->mTextMsgQueue.empty())
- {
- std::string message = session->mTextMsgQueue.front();
- session->mTextMsgQueue.pop();
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SendMessage.1\">"
- << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
- << "<MessageHeader>text/HTML</MessageHeader>"
- << "<MessageBody>" << message << "</MessageBody>"
- << "</Request>"
- << "\n\n\n";
- }
- writeString(stream.str());
- }
- }
- else
+ if (mVoiceModule)
{
- // Session isn't connected yet, defer until later.
+ return mVoiceModule->getCurrentChannel();
}
-}
-
-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;
+ return std::string();
}
}
-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;
-}
+//---------------------------------------
+// invitations
-bool LLVoiceClient::isOnlineSIP(const LLUUID &id)
+void LLVoiceClient::callUser(const LLUUID &uuid)
{
- 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;
+ if (mVoiceModule) mVoiceModule->callUser(uuid);
}
-// Returns true if the indicated participant in the current audio session is really an SL avatar.
-// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls.
-bool LLVoiceClient::isParticipantAvatar(const LLUUID &id)
+bool LLVoiceClient::answerInvite(std::string &channelHandle)
{
- bool result = true;
- sessionState *session = findSession(id);
-
- if(session != NULL)
+ if (mVoiceModule)
{
- // this is a p2p session with the indicated caller, or the session with the specified UUID.
- if(session->mSynthesizedCallerID)
- result = false;
+ return mVoiceModule->answerInvite(channelHandle);
}
else
{
- // Didn't find a matching session -- check the current audio session for a matching participant
- if(mAudioSession != NULL)
- {
- participantState *participant = findParticipantByID(id);
- if(participant != NULL)
- {
- result = participant->isAvatar();
- }
- }
- }
-
- return result;
-}
-
-// Returns true if calling back the session URI after the session has closed is possible.
-// Currently this will be false only for PSTN P2P calls.
-bool LLVoiceClient::isSessionCallBackPossible(const LLUUID &session_id)
-{
- bool result = true;
- sessionState *session = findSession(session_id);
-
- if(session != NULL)
- {
- result = session->isCallBackPossible();
- }
-
- return result;
-}
-
-// Returns true if the session can accepte text IM's.
-// Currently this will be false only for PSTN P2P calls.
-bool LLVoiceClient::isSessionTextIMPossible(const LLUUID &session_id)
-{
- bool result = true;
- sessionState *session = findSession(session_id);
-
- if(session != NULL)
- {
- result = session->isTextIMPossible();
- }
-
- 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 false;
}
-
- return result;
-}
-
-std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
-{
- return avatar->getFullname();
}
-std::string LLVoiceClient::sipURIFromName(std::string &name)
+void LLVoiceClient::declineInvite(std::string &channelHandle)
{
- std::string result;
- result = "sip:";
- result += name;
- result += "@";
- result += mVoiceSIPURIHostName;
-
-// LLStringUtil::toLower(result);
-
- return result;
+ if (mVoiceModule) mVoiceModule->declineInvite(channelHandle);
}
-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;
-}
+//------------------------------------------
+// Volume/gain
-bool LLVoiceClient::inSpatialChannel(void)
-{
- bool result = false;
-
- if(mAudioSession)
- result = mAudioSession->mIsSpatial;
-
- return result;
-}
-std::string LLVoiceClient::getAudioSessionURI()
+void LLVoiceClient::setVoiceVolume(F32 volume)
{
- std::string result;
-
- if(mAudioSession)
- result = mAudioSession->mSIPURI;
-
- return result;
+ if (mVoiceModule) mVoiceModule->setVoiceVolume(volume);
}
-std::string LLVoiceClient::getAudioSessionHandle()
+void LLVoiceClient::setMicGain(F32 volume)
{
- std::string result;
-
- if(mAudioSession)
- result = mAudioSession->mHandle;
-
- return result;
+ if (mVoiceModule) mVoiceModule->setMicGain(volume);
}
-/////////////////////////////
-// Sending updates of current state
+//------------------------------------------
+// enable/disable voice features
-void LLVoiceClient::enforceTether(void)
+bool LLVoiceClient::voiceEnabled()
{
- 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)
+ if (mVoiceModule)
{
- mCameraPosition = tethered;
- mSpatialCoordsDirty = true;
+ return mVoiceModule->voiceEnabled();
}
-}
-
-void LLVoiceClient::updatePosition(void)
-{
-
- if(gVoiceClient)
+ else
{
- 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
- }
+ return false;
}
}
-void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
+void LLVoiceClient::setVoiceEnabled(bool enabled)
{
- mCameraRequestedPosition = position;
-
- if(mCameraVelocity != velocity)
- {
- mCameraVelocity = velocity;
- mSpatialCoordsDirty = true;
- }
-
- if(mCameraRot != rot)
- {
- mCameraRot = rot;
- mSpatialCoordsDirty = true;
- }
+ if (mVoiceModule) mVoiceModule->setVoiceEnabled(enabled);
}
-void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
+void LLVoiceClient::setLipSyncEnabled(BOOL enabled)
{
- 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;
- }
+ if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled);
}
-bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name)
+BOOL LLVoiceClient::lipSyncEnabled()
{
- bool result = false;
-
- if(region)
+ if (mVoiceModule)
{
- name = region->getName();
+ return mVoiceModule->lipSyncEnabled();
}
-
- if(!name.empty())
- result = true;
-
- return result;
-}
-
-void LLVoiceClient::leaveChannel(void)
-{
- if(getState() == stateRunning)
+ else
{
- LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL;
- mChannelName.clear();
- sessionTerminate();
+ return false;
}
}
void LLVoiceClient::setMuteMic(bool muted)
{
- mMuteMic = muted;
+ if (mVoiceModule) mVoiceModule->setMuteMic(muted);
}
-bool LLVoiceClient::getMuteMic() const
-{
- return mMuteMic;
-}
+
+// ----------------------------------------------
+// PTT
void LLVoiceClient::setUserPTTState(bool ptt)
{
- mUserPTTState = ptt;
+ if (mVoiceModule) mVoiceModule->setUserPTTState(ptt);
}
bool LLVoiceClient::getUserPTTState()
{
- return mUserPTTState;
-}
-
-void LLVoiceClient::toggleUserPTTState(void)
-{
- mUserPTTState = !mUserPTTState;
-}
-
-void LLVoiceClient::setVoiceEnabled(bool enabled)
-{
- if (enabled != mVoiceEnabled)
- {
- mVoiceEnabled = enabled;
- LLVoiceClientStatusObserver::EStatusType status;
-
- if (enabled)
- {
- LLVoiceChannel::getCurrentVoiceChannel()->activate();
- status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED;
- }
- 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();
- status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED;
- }
-
- notifyStatusObservers(status);
- }
-}
-
-bool LLVoiceClient::voiceEnabled()
-{
- return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice");
-}
-
-void LLVoiceClient::setLipSyncEnabled(BOOL enabled)
-{
- mLipSyncEnabled = enabled;
-}
-
-BOOL LLVoiceClient::lipSyncEnabled()
-{
-
- if ( mVoiceEnabled && stateDisabled != getState() )
+ if (mVoiceModule)
{
- return mLipSyncEnabled;
+ return mVoiceModule->getUserPTTState();
}
else
{
- return FALSE;
+ return false;
}
}
void LLVoiceClient::setUsePTT(bool usePTT)
{
- if(usePTT && !mUsePTT)
- {
- // When the user turns on PTT, reset the current state.
- mUserPTTState = false;
- }
- mUsePTT = usePTT;
+ if (mVoiceModule) mVoiceModule->setUsePTT(usePTT);
}
void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle)
{
- if(!PTTIsToggle && mPTTIsToggle)
- {
- // When the user turns off toggle, reset the current state.
- mUserPTTState = false;
- }
-
- mPTTIsToggle = PTTIsToggle;
+ if (mVoiceModule) mVoiceModule->setPTTIsToggle(PTTIsToggle);
}
bool LLVoiceClient::getPTTIsToggle()
{
- return mPTTIsToggle;
-}
-
-void LLVoiceClient::setPTTKey(std::string &key)
-{
- if(key == "MiddleMouse")
+ if (mVoiceModule)
{
- mPTTIsMiddleMouse = true;
+ return mVoiceModule->getPTTIsToggle();
}
- else
- {
- mPTTIsMiddleMouse = false;
- if(!LLKeyboard::keyFromString(key, &mPTTKey))
- {
- // If the call failed, don't match any key.
- key = KEY_NONE;
- }
+ else {
+ return false;
}
-}
-void LLVoiceClient::setEarLocation(S32 loc)
-{
- if(mEarLocation != loc)
- {
- LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
-
- mEarLocation = loc;
- mSpatialCoordsDirty = true;
- }
}
-void LLVoiceClient::setVoiceVolume(F32 volume)
+void LLVoiceClient::inputUserControlState(bool down)
{
- int scaled_volume = scale_speaker_volume(volume);
-
- if(scaled_volume != mSpeakerVolume)
- {
- if((scaled_volume == 0) || (mSpeakerVolume == 0))
- {
- mSpeakerMuteDirty = true;
- }
-
- mSpeakerVolume = scaled_volume;
- mSpeakerVolumeDirty = true;
- }
+ if (mVoiceModule) mVoiceModule->inputUserControlState(down);
}
-void LLVoiceClient::setMicGain(F32 volume)
+void LLVoiceClient::toggleUserPTTState(void)
{
- int scaled_volume = scale_mic_volume(volume);
-
- if(scaled_volume != mMicVolume)
- {
- mMicVolume = scaled_volume;
- mMicVolumeDirty = true;
- }
+ if (mVoiceModule) mVoiceModule->toggleUserPTTState();
}
void LLVoiceClient::keyDown(KEY key, MASK mask)
{
- if (gKeyboard->getKeyRepeated(key))
- {
- // ignore auto-repeat keys
- return;
- }
-
- if(!mPTTIsMiddleMouse)
- {
- bool down = (mPTTKey != KEY_NONE)
- && gKeyboard->getKeyDown(mPTTKey);
- inputUserControlState(down);
- }
+ if (mVoiceModule) mVoiceModule->keyDown(key, mask);
}
void LLVoiceClient::keyUp(KEY key, MASK mask)
{
- if(!mPTTIsMiddleMouse)
- {
- bool down = (mPTTKey != KEY_NONE)
- && gKeyboard->getKeyDown(mPTTKey);
- inputUserControlState(down);
- }
-}
-void LLVoiceClient::inputUserControlState(bool down)
-{
- if(mPTTIsToggle)
- {
- if(down) // toggle open-mic state on 'down'
- {
- toggleUserPTTState();
- }
- }
- else // set open-mic state as an absolute
- {
- setUserPTTState(down);
- }
+ if (mVoiceModule) mVoiceModule->keyUp(key, mask);
}
void LLVoiceClient::middleMouseState(bool down)
{
- if(mPTTIsMiddleMouse)
- {
- inputUserControlState(down);
- }
-}
-
-/////////////////////////////
-// Accessors for data related to nearby speakers
-BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
-{
- 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;
+ if (mVoiceModule) mVoiceModule->middleMouseState(down);
}
-BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
-{
- BOOL result = FALSE;
- participantState *participant = findParticipantByID(id);
- if(participant)
- {
- if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
- {
- participant->mIsSpeaking = FALSE;
- }
- result = participant->mIsSpeaking;
- }
-
- return result;
-}
+//-------------------------------------------
+// nearby speaker accessors
-BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
+BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
{
- BOOL result = FALSE;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
+ if (mVoiceModule)
{
- result = participant->mIsModeratorMuted;
- }
-
- return result;
-}
-
-F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
-{
- F32 result = 0;
- participantState *participant = findParticipantByID(id);
- if(participant)
+ return mVoiceModule->getVoiceEnabled(id);
+ }
+ else
{
- result = participant->mPower;
+ return FALSE;
}
-
- return result;
}
-
std::string LLVoiceClient::getDisplayName(const LLUUID& id)
{
- std::string result;
- participantState *participant = findParticipantByID(id);
- if(participant)
- {
- result = participant->mDisplayName;
- }
-
- return result;
-}
-
-
-BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
-{
- BOOL result = FALSE;
-
- 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;
-}
-
-BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
-{
- BOOL result = FALSE;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
- {
- result = participant->mOnMuteList;
- }
-
- return result;
-}
-
-// 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)
-{
- F32 result = 0.0f;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
- {
- S32 ires = 100; // nominal default volume
-
- if(participant->mIsSelf)
- {
- // Always make it look like the user's own volume is set at the default.
- }
- else if(participant->mUserVolume != -1)
- {
- // Use the internal volume
- ires = participant->mUserVolume;
-
- // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
-// LL_DEBUGS("Voice") << "mapping from mUserVolume " << ires << LL_ENDL;
- }
- else if(participant->mVolume != -1)
- {
- // Map backwards from vivox volume
-
- // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
-// LL_DEBUGS("Voice") << "mapping from mVolume " << participant->mVolume << LL_ENDL;
-
- if(participant->mVolume < 56)
- {
- ires = (participant->mVolume * 100) / 56;
- }
- else
- {
- ires = (((participant->mVolume - 56) * 300) / (100 - 56)) + 100;
- }
- }
- result = sqrtf(((F32)ires) / 400.f);
- }
-
- // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
-// LL_DEBUGS("Voice") << "returning " << result << LL_ENDL;
-
- return result;
-}
-
-void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
-{
- if(mAudioSession)
- {
- participantState *participant = findParticipantByID(id);
- if (participant)
- {
- // volume can amplify by as much as 4x!
- S32 ivol = (S32)(400.f * volume * volume);
- participant->mUserVolume = llclamp(ivol, 0, 400);
- participant->mVolumeDirty = TRUE;
- mAudioSession->mVolumeDirty = TRUE;
- }
- }
-}
-
-std::string LLVoiceClient::getGroupID(const LLUUID& id)
-{
- std::string result;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
- {
- result = participant->mGroupID;
- }
-
- return result;
-}
-
-BOOL LLVoiceClient::getAreaVoiceDisabled()
-{
- return mAreaVoiceDisabled;
-}
-
-void LLVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame)
-{
-// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL;
-
- if(!mMainSessionGroupHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
- << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
- << "<RecordingControlType>Start</RecordingControlType>"
- << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>"
- << "<Filename>" << "" << "</Filename>"
- << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>"
- << "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>"
- << "</Request>\n\n\n";
-
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::recordingLoopSave(const std::string& filename)
-{
-// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL;
-
- if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
- << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
- << "<RecordingControlType>Flush</RecordingControlType>"
- << "<Filename>" << filename << "</Filename>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::recordingStop()
-{
-// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL;
-
- if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
- << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
- << "<RecordingControlType>Stop</RecordingControlType>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::filePlaybackStart(const std::string& filename)
-{
-// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL;
-
- if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
- << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
- << "<RecordingControlType>Start</RecordingControlType>"
- << "<Filename>" << filename << "</Filename>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::filePlaybackStop()
-{
-// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL;
-
- if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
- {
- std::ostringstream stream;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
- << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
- << "<RecordingControlType>Stop</RecordingControlType>"
- << "</Request>\n\n\n";
-
- writeString(stream.str());
- }
-}
-
-void LLVoiceClient::filePlaybackSetPaused(bool paused)
-{
- // TODO: Implement once Vivox gives me a sample
-}
-
-void LLVoiceClient::filePlaybackSetMode(bool vox, float speed)
-{
- // TODO: Implement once Vivox gives me a sample
-}
-
-LLVoiceClient::sessionState::sessionState() :
- mMediaStreamState(streamStateUnknown),
- mTextStreamState(streamStateUnknown),
- mCreateInProgress(false),
- mMediaConnectInProgress(false),
- mVoiceInvitePending(false),
- mTextInvitePending(false),
- mSynthesizedCallerID(false),
- mIsChannel(false),
- mIsSpatial(false),
- mIsP2P(false),
- mIncoming(false),
- mVoiceEnabled(false),
- mReconnect(false),
- mVolumeDirty(false),
- mParticipantsChanged(false)
-{
-}
-
-LLVoiceClient::sessionState::~sessionState()
-{
- removeAllParticipants();
-}
-
-bool LLVoiceClient::sessionState::isCallBackPossible()
-{
- // This may change to be explicitly specified by vivox in the future...
- // Currently, only PSTN P2P calls cannot be returned.
- // Conveniently, this is also the only case where we synthesize a caller UUID.
- return !mSynthesizedCallerID;
-}
-
-bool LLVoiceClient::sessionState::isTextIMPossible()
-{
- // This may change to be explicitly specified by vivox in the future...
- return !mSynthesizedCallerID;
-}
-
-
-LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void)
-{
- return mSessions.begin();
-}
-
-LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void)
-{
- return mSessions.end();
-}
-
-
-LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle)
-{
- sessionState *result = NULL;
- sessionMap::iterator iter = mSessionsByHandle.find(&handle);
- if(iter != mSessionsByHandle.end())
- {
- result = iter->second;
- }
-
- return result;
-}
-
-LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri)
-{
- sessionState *result = NULL;
- for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
- {
- sessionState *session = *iter;
- if(session->mCreateInProgress && (session->mSIPURI == uri))
- {
- result = session;
- break;
- }
- }
-
- return result;
-}
-
-LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id)
-{
- sessionState *result = NULL;
-
- for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
- {
- sessionState *session = *iter;
- if((session->mCallerID == participant_id) || (session->mIMSessionID == participant_id))
- {
- result = session;
- break;
- }
- }
-
- return result;
-}
-
-LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle)
-{
- sessionState *result = NULL;
-
- if(handle.empty())
- {
- // No handle supplied.
- // Check whether there's already a session with this URI
- for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
- {
- sessionState *s = *iter;
- if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri))
- {
- // TODO: I need to think about this logic... it's possible that this case should raise an internal error.
- result = s;
- break;
- }
- }
- }
- else // (!handle.empty())
- {
- // Check for an existing session with this handle
- sessionMap::iterator iter = mSessionsByHandle.find(&handle);
-
- if(iter != mSessionsByHandle.end())
- {
- result = iter->second;
- }
- }
-
- if(!result)
+ if (mVoiceModule)
{
- // No existing session found.
-
- LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL;
- result = new sessionState();
- result->mSIPURI = uri;
- result->mHandle = handle;
-
- mSessions.insert(result);
-
- if(!result->mHandle.empty())
- {
- mSessionsByHandle.insert(sessionMap::value_type(&(result->mHandle), result));
- }
+ return mVoiceModule->getDisplayName(id);
}
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;
+ return std::string();
}
-
- verifySessionState();
-
- return result;
}
-void LLVoiceClient::setSessionHandle(sessionState *session, const std::string &handle)
+BOOL LLVoiceClient::isParticipantAvatar(const LLUUID& id)
{
- // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly.
-
- if(!session->mHandle.empty())
+ if (mVoiceModule)
{
- // Remove session from the map if it should have been there.
- sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle));
- if(iter != mSessionsByHandle.end())
- {
- if(iter->second != session)
- {
- LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL;
- }
-
- mSessionsByHandle.erase(iter);
- }
- else
- {
- LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL;
- }
+ return mVoiceModule->isParticipantAvatar(id);
}
-
- session->mHandle = handle;
-
- if(!handle.empty())
+ else
{
- mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session));
+ return FALSE;
}
-
- verifySessionState();
}
-void LLVoiceClient::setSessionURI(sessionState *session, const std::string &uri)
+BOOL LLVoiceClient::isOnlineSIP(const LLUUID& id)
{
- // There used to be a map of session URIs to sessions, which made this complex....
- session->mSIPURI = uri;
-
- verifySessionState();
-}
-
-void LLVoiceClient::deleteSession(sessionState *session)
-{
- // Remove the session from the handle map
- if(!session->mHandle.empty())
- {
- sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle));
- if(iter != mSessionsByHandle.end())
- {
- if(iter->second != session)
- {
- LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL;
- }
- mSessionsByHandle.erase(iter);
- }
- }
-
- // Remove the session from the URI map
- mSessions.erase(session);
-
- // At this point, the session should be unhooked from all lists and all state should be consistent.
- verifySessionState();
-
- // If this is the current audio session, clean up the pointer which will soon be dangling.
- if(mAudioSession == session)
+ if (mVoiceModule)
{
- mAudioSession = NULL;
- mAudioSessionChanged = true;
+ return mVoiceModule->isOnlineSIP(id);
}
-
- // ditto for the next audio session
- if(mNextAudioSession == session)
+ else
{
- mNextAudioSession = NULL;
+ return FALSE;
}
-
- // delete the session
- delete session;
}
-void LLVoiceClient::deleteAllSessions()
+BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
{
- LL_DEBUGS("Voice") << "called" << LL_ENDL;
-
- while(!mSessions.empty())
+ if (mVoiceModule)
{
- deleteSession(*(sessionsBegin()));
+ return mVoiceModule->getIsSpeaking(id);
}
-
- if(!mSessionsByHandle.empty())
+ else
{
- LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL;
+ return FALSE;
}
}
-void LLVoiceClient::verifySessionState(void)
+BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
{
- // This is mostly intended for debugging problems with session state management.
- LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL;
-
- for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
- {
- sessionState *session = *iter;
-
- LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL;
-
- if(!session->mHandle.empty())
- {
- // every session with a non-empty handle needs to be in the handle map
- sessionMap::iterator i2 = mSessionsByHandle.find(&(session->mHandle));
- if(i2 == mSessionsByHandle.end())
- {
- LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL;
- }
- else
- {
- if(i2->second != session)
- {
- LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL;
- }
- }
- }
- }
-
- // check that every entry in the handle map points to a valid session in the session set
- for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++)
+ if (mVoiceModule)
{
- sessionState *session = iter->second;
- sessionIterator i2 = mSessions.find(session);
- if(i2 == mSessions.end())
- {
- LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL;
- }
- else
- {
- if(session->mHandle != (*i2)->mHandle)
- {
- LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL;
- }
- }
+ return mVoiceModule->getIsModeratorMuted(id);
}
-}
-
-LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) :
- mURI(uri)
-{
- mOnlineSL = false;
- mOnlineSLim = false;
- mCanSeeMeOnline = true;
- mHasBlockListEntry = false;
- mHasAutoAcceptListEntry = false;
- mNameResolved = false;
- mInVivoxBuddies = false;
- mInSLFriends = false;
- mNeedsNameUpdate = false;
-}
-
-void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName)
-{
- buddyListEntry *buddy = addBuddy(uri, displayName);
- buddy->mInVivoxBuddies = true;
-}
-
-LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri)
-{
- std::string empty;
- buddyListEntry *buddy = addBuddy(uri, empty);
- if(buddy->mDisplayName.empty())
+ else
{
- buddy->mNameResolved = false;
+ return FALSE;
}
- return buddy;
}
-LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName)
-{
- buddyListEntry *result = NULL;
- buddyListMap::iterator iter = mBuddyListMap.find(&uri);
-
- if(iter != mBuddyListMap.end())
- {
- // Found a matching buddy already in the map.
- LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL;
- result = iter->second;
- }
-
- if(!result)
+F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
+{
+ if (mVoiceModule)
{
- // 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 mVoiceModule->getCurrentPower(id);
}
-
- return result;
-}
-
-LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri)
-{
- buddyListEntry *result = NULL;
- buddyListMap::iterator iter = mBuddyListMap.find(&uri);
- if(iter != mBuddyListMap.end())
+ else
{
- result = iter->second;
+ return 0.0;
}
-
- return result;
}
-LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id)
+BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
{
- buddyListEntry *result = NULL;
- buddyListMap::iterator iter;
-
- for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
+ if (mVoiceModule)
{
- if(iter->second->mUUID == id)
- {
- result = iter->second;
- break;
- }
+ return mVoiceModule->getOnMuteList(id);
}
-
- return result;
-}
-
-LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name)
-{
- buddyListEntry *result = NULL;
- buddyListMap::iterator iter;
-
- for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
+ else
{
- if(iter->second->mDisplayName == name)
- {
- result = iter->second;
- break;
- }
+ return FALSE;
}
-
- return result;
}
-void LLVoiceClient::deleteBuddy(const std::string &uri)
+F32 LLVoiceClient::getUserVolume(const LLUUID& id)
{
- buddyListMap::iterator iter = mBuddyListMap.find(&uri);
- if(iter != mBuddyListMap.end())
+ if (mVoiceModule)
{
- LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL;
- buddyListEntry *buddy = iter->second;
- mBuddyListMap.erase(iter);
- delete buddy;
+ return mVoiceModule->getUserVolume(id);
}
else
{
- LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL;
- }
-
-}
-
-void LLVoiceClient::deleteAllBuddies(void)
-{
- while(!mBuddyListMap.empty())
- {
- deleteBuddy(*(mBuddyListMap.begin()->first));
+ return 0.0;
}
-
- // Don't want to correlate with friends list when we've emptied the buddy list.
- mBuddyListMapPopulated = false;
-
- // Don't want to correlate with friends list when we've reset the block rules.
- mBlockRulesListReceived = false;
- mAutoAcceptRulesListReceived = false;
}
-void LLVoiceClient::deleteAllBlockRules(void)
+void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
{
- // Clear the block list entry flags from all local buddy list entries
- buddyListMap::iterator buddy_it;
- for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
- {
- buddy_it->second->mHasBlockListEntry = false;
- }
+ if (mVoiceModule) mVoiceModule->setUserVolume(id, volume);
}
-void LLVoiceClient::deleteAllAutoAcceptRules(void)
-{
- // Clear the auto-accept list entry flags from all local buddy list entries
- buddyListMap::iterator buddy_it;
- for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
- {
- buddy_it->second->mHasAutoAcceptListEntry = false;
- }
-}
+//--------------------------------------------------
+// status observers
-void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly)
+void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
{
- buddyListEntry *buddy = NULL;
-
- // blockMask is the SIP URI of a friends list entry
- buddyListMap::iterator iter = mBuddyListMap.find(&blockMask);
- if(iter != mBuddyListMap.end())
- {
- LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL;
- buddy = iter->second;
- }
-
- if(buddy == NULL)
- {
- LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL;
- buddy = addBuddy(blockMask);
- }
-
- if(buddy != NULL)
- {
- buddy->mHasBlockListEntry = true;
- }
+ if (mVoiceModule) mVoiceModule->addObserver(observer);
}
-void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy)
+void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
{
- buddyListEntry *buddy = NULL;
-
- // blockMask is the SIP URI of a friends list entry
- buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask);
- if(iter != mBuddyListMap.end())
- {
- LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL;
- buddy = iter->second;
- }
-
- if(buddy == NULL)
- {
- LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL;
- buddy = addBuddy(autoAcceptMask);
- }
-
- if(buddy != NULL)
- {
- buddy->mHasAutoAcceptListEntry = true;
- }
+ if (mVoiceModule) mVoiceModule->removeObserver(observer);
}
-void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString)
+void LLVoiceClient::addObserver(LLFriendObserver* observer)
{
- // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done.
- mBlockRulesListReceived = true;
+ if (mVoiceModule) mVoiceModule->addObserver(observer);
}
-void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString)
+void LLVoiceClient::removeObserver(LLFriendObserver* observer)
{
- // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done.
- mAutoAcceptRulesListReceived = true;
+ if (mVoiceModule) mVoiceModule->removeObserver(observer);
}
void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
{
- mParticipantObservers.insert(observer);
+ if (mVoiceModule) mVoiceModule->addObserver(observer);
}
void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
{
- mParticipantObservers.erase(observer);
-}
-
-void LLVoiceClient::notifyParticipantObservers()
-{
- for (observer_set_t::iterator it = mParticipantObservers.begin();
- it != mParticipantObservers.end();
- )
- {
- LLVoiceClientParticipantObserver* observer = *it;
- observer->onChange();
- // In case onChange() deleted an entry.
- it = mParticipantObservers.upper_bound(observer);
- }
-}
-
-void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
-{
- mStatusObservers.insert(observer);
+ if (mVoiceModule) mVoiceModule->removeObserver(observer);
}
-void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
-{
- mStatusObservers.erase(observer);
-}
-
-void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
+std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
{
- if(mAudioSession)
- {
- if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
- {
- 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.
- 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 " << getAudioSessionURI()
- << (inSpatialChannel()?", proximal is true":", proximal is false")
- << LL_ENDL;
-
- for (status_observer_set_t::iterator it = mStatusObservers.begin();
- it != mStatusObservers.end();
- )
+ if (mVoiceModule)
{
- LLVoiceClientStatusObserver* observer = *it;
- observer->onChange(status, getAudioSessionURI(), inSpatialChannel());
- // In case onError() deleted an entry.
- it = mStatusObservers.upper_bound(observer);
+ return mVoiceModule->sipURIFromID(id);
}
-
-}
-
-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();
- )
+ else
{
- LLFriendObserver* observer = *it;
- it++;
- // The only friend-related thing we notify on is online/offline transitions.
- observer->changed(LLFriendObserver::ONLINE);
+ return std::string();
}
}
-void LLVoiceClient::lookupName(const LLUUID &id)
-{
- BOOL is_group = FALSE;
- gCacheName->get(id, is_group, &LLVoiceClient::onAvatarNameLookup);
-}
-//static
-void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group)
-{
- if(gVoiceClient)
- {
- std::string name = llformat("%s %s", first.c_str(), last.c_str());
- gVoiceClient->avatarNameResolved(id, name);
- }
-}
+///////////////////
+// version checking
-void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name)
+class LLViewerRequiredVoiceVersion : public LLHTTPNode
{
- // If the avatar whose name just resolved is on our friends list, resync the friends list.
- if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL)
- {
- mFriendsListDirty = true;
- }
-
- // Iterate over all sessions.
- for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ static BOOL sAlertedUser;
+ virtual void post(
+ LLHTTPNode::ResponsePtr response,
+ const LLSD& context,
+ const LLSD& input) const
{
- 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)
+ //You received this messsage (most likely on region cross or
+ //teleport)
+ if ( input.has("body") && input["body"].has("major_version") )
{
- // 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)
+ int major_voice_version =
+ input["body"]["major_version"].asInteger();
+ // int minor_voice_version =
+ // input["body"]["minor_version"].asInteger();
+ LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
+
+ if (major_voice_version > 1)
{
- 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);
+ if (!sAlertedUser)
+ {
+ //sAlertedUser = TRUE;
+ LLNotificationsUtil::add("VoiceVersionMismatch");
+ gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener
+ }
}
-
}
}
-}
+};
class LLViewerParcelVoiceInfo : public LLHTTPNode
{
virtual void post(
- LLHTTPNode::ResponsePtr response,
- const LLSD& context,
- const LLSD& input) const
+ LLHTTPNode::ResponsePtr response,
+ const LLSD& context,
+ const LLSD& input) const
{
//the parcel you are in has changed something about its
//voice information
-
+
//this is a misnomer, as it can also be when you are not in
//a parcel at all. Should really be something like
//LLViewerVoiceInfoChanged.....
if ( input.has("body") )
{
LLSD body = input["body"];
-
+
//body has "region_name" (str), "parcel_local_id"(int),
//"voice_credentials" (map).
-
+
//body["voice_credentials"] has "channel_uri" (str),
//body["voice_credentials"] has "channel_credentials" (str)
-
+
//if we really wanted to be extra careful,
//we'd check the supplied
//local parcel id to make sure it's for the same parcel
@@ -7026,7 +743,7 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode
LLSD voice_credentials = body["voice_credentials"];
std::string uri;
std::string credentials;
-
+
if ( voice_credentials.has("channel_uri") )
{
uri = voice_credentials["channel_uri"].asString();
@@ -7034,51 +751,21 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode
if ( voice_credentials.has("channel_credentials") )
{
credentials =
- voice_credentials["channel_credentials"].asString();
+ voice_credentials["channel_credentials"].asString();
}
-
- gVoiceClient->setSpatialChannel(uri, credentials);
+
+ LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials);
}
}
}
};
-class LLViewerRequiredVoiceVersion : public LLHTTPNode
-{
- static BOOL sAlertedUser;
- virtual void post(
- LLHTTPNode::ResponsePtr response,
- const LLSD& context,
- const LLSD& input) const
- {
- //You received this messsage (most likely on region cross or
- //teleport)
- if ( input.has("body") && input["body"].has("major_version") )
- {
- int major_voice_version =
- input["body"]["major_version"].asInteger();
-// int minor_voice_version =
-// input["body"]["minor_version"].asInteger();
-
- if (gVoiceClient &&
- (major_voice_version > VOICE_MAJOR_VERSION) )
- {
- if (!sAlertedUser)
- {
- //sAlertedUser = TRUE;
- LLNotificationsUtil::add("VoiceVersionMismatch");
- gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener
- }
- }
- }
- }
-};
BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE;
LLHTTPRegistration<LLViewerParcelVoiceInfo>
- gHTTPRegistrationMessageParcelVoiceInfo(
- "/message/ParcelVoiceInfo");
+gHTTPRegistrationMessageParcelVoiceInfo(
+ "/message/ParcelVoiceInfo");
LLHTTPRegistration<LLViewerRequiredVoiceVersion>
- gHTTPRegistrationMessageRequiredVoiceVersion(
- "/message/RequiredVoiceVersion");
+gHTTPRegistrationMessageRequiredVoiceVersion(
+ "/message/RequiredVoiceVersion");
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index 075834f7e0..8af12f9d38 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -17,8 +17,7 @@
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -33,7 +32,6 @@
#define LL_VOICE_CLIENT_H
class LLVOAvatar;
-class LLVivoxProtocolParser;
#include "lliopipe.h"
#include "llpumpio.h"
@@ -42,9 +40,14 @@ class LLVivoxProtocolParser;
#include "v3math.h"
#include "llframetimer.h"
#include "llviewerregion.h"
-#include "m3math.h" // LLMatrix3
+#include "llcallingcard.h" // for LLFriendObserver
+#include "llsecapi.h"
+
+// devices
+
+typedef std::vector<std::string> LLVoiceDeviceList;
+
-class LLFriendObserver;
class LLVoiceClientParticipantObserver
{
public:
@@ -52,6 +55,9 @@ public:
virtual void onChange() = 0;
};
+
+///////////////////////////////////
+/// @class LLVoiceClientStatusObserver
class LLVoiceClientStatusObserver
{
public:
@@ -65,11 +71,7 @@ public:
STATUS_JOINED,
STATUS_LEFT_CHANNEL,
STATUS_VOICE_DISABLED,
-
- // Adding STATUS_VOICE_ENABLED as pair status for STATUS_VOICE_DISABLED
- // See LLVoiceClient::setVoiceEnabled()
STATUS_VOICE_ENABLED,
-
BEGIN_ERROR_STATUS,
ERROR_CHANNEL_FULL,
ERROR_CHANNEL_LOCKED,
@@ -83,683 +85,319 @@ public:
static std::string status2string(EStatusType inStatus);
};
-class LLVoiceClient: public LLSingleton<LLVoiceClient>
+struct LLVoiceVersionInfo
{
- LOG_CLASS(LLVoiceClient);
- public:
- LLVoiceClient();
- ~LLVoiceClient();
-
- public:
- static void init(LLPumpIO *pump); // Call this once at application startup (creates connector)
- static void terminate(); // Call this to clean up during shutdown
-
- protected:
- bool writeString(const std::string &str);
-
- public:
-
- static F32 OVERDRIVEN_POWER_LEVEL;
-
- void updateSettings(); // call after loading settings and whenever they change
-
- void getCaptureDevicesSendMessage();
- void getRenderDevicesSendMessage();
-
- void clearCaptureDevices();
- void addCaptureDevice(const std::string& name);
- void setCaptureDevice(const std::string& name);
-
- void clearRenderDevices();
- void addRenderDevice(const std::string& name);
- void setRenderDevice(const std::string& name);
-
- void tuningStart();
- void tuningStop();
- bool inTuningMode();
- bool inTuningStates();
-
- void tuningRenderStartSendMessage(const std::string& name, bool loop);
- void tuningRenderStopSendMessage();
-
- void tuningCaptureStartSendMessage(int duration);
- void tuningCaptureStopSendMessage();
-
- void tuningSetMicVolume(float volume);
- void tuningSetSpeakerVolume(float volume);
- float tuningGetEnergy(void);
-
- // This returns true when it's safe to bring up the "device settings" dialog in the prefs.
- // i.e. when the daemon is running and connected, and the device lists are populated.
- bool deviceSettingsAvailable();
-
- // Requery the vivox daemon for the current list of input/output devices.
- // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed
- // (use this if you want to know when it's done).
- // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim.
- void refreshDeviceLists(bool clearCurrentList = true);
-
- // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch.
- void daemonDied();
-
- // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away.
- void giveUp();
-
- /////////////////////////////
- // Response/Event handlers
- 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 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 &notificationType);
- 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
-static void updatePosition(void);
- void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot);
- void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot);
- bool channelFromRegion(LLViewerRegion *region, std::string &name);
- void leaveChannel(void); // call this on logout or teleport begin
-
-
- void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state.
- bool getMuteMic() const;
- void setUserPTTState(bool ptt);
- bool getUserPTTState();
- void toggleUserPTTState(void);
- void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs
- void setVoiceEnabled(bool enabled);
- static bool voiceEnabled();
- void setUsePTT(bool usePTT);
- void setPTTIsToggle(bool PTTIsToggle);
- bool getPTTIsToggle();
- void setPTTKey(std::string &key);
- void setEarLocation(S32 loc);
- void setVoiceVolume(F32 volume);
- void setMicGain(F32 volume);
- void setUserVolume(const LLUUID& id, F32 volume); // sets volume for specified agent, from 0-1 (where .5 is nominal)
- void setLipSyncEnabled(BOOL enabled);
- BOOL lipSyncEnabled();
-
- // PTT key triggering
- void keyDown(KEY key, MASK mask);
- void keyUp(KEY key, MASK mask);
- void middleMouseState(bool down);
-
- // Return the version of the Vivox library
- std::string getAPIVersion() const { return mAPIVersion; }
-
- /////////////////////////////
- // Accessors for data related to nearby speakers
- BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar
- 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 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);
- 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();
- bool isAvatar();
-
- std::string mURI;
- LLUUID mAvatarID;
- std::string mAccountName;
- std::string mDisplayName;
- LLFrameTimer mSpeakingTimeout;
- F32 mLastSpokeTimestamp;
- F32 mPower;
- int mVolume;
- std::string mGroupID;
- 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;
- bool mIsSelf;
- };
- typedef std::map<const std::string *, participantState*, stringMapComparitor> participantMap;
-
- typedef std::map<const LLUUID *, participantState*, uuidMapComparitor> participantUUIDMap;
-
- enum streamState
- {
- streamStateUnknown = 0,
- streamStateIdle = 1,
- streamStateConnected = 2,
- streamStateRinging = 3,
- };
-
- 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);
-
- bool isCallBackPossible();
- bool isTextIMPossible();
-
- std::string mHandle;
- std::string mGroupHandle;
- std::string mSIPURI;
- std::string mAlias;
- std::string mName;
- std::string mAlternateSIPURI;
- std::string mHash; // Channel password
- std::string mErrorStatusString;
- std::queue<std::string> mTextMsgQueue;
-
- LLUUID mIMSessionID;
- LLUUID mCallerID;
- int mErrorStatusCode;
- int mMediaStreamState;
- int mTextStreamState;
- bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet.
- bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet.
- bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup)
- bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup)
- bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup.
- bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN)
- bool mIsSpatial; // True for spatial channels
- bool mIsP2P;
- bool mIncoming;
- bool mVoiceEnabled;
- bool mReconnect; // Whether we should try to reconnect to this session if it's dropped
- // Set to true when the mute state of someone in the participant list changes.
- // The code will have to walk the list to find the changed participant(s).
- bool mVolumeDirty;
-
- bool mParticipantsChanged;
- participantMap mParticipantsByURI;
- participantUUIDMap mParticipantsByUUID;
- };
-
- participantState *findParticipantByID(const LLUUID& id);
- participantMap *getParticipantList(void);
-
- typedef std::map<const std::string*, sessionState*, stringMapComparitor> sessionMap;
- typedef std::set<sessionState*> sessionSet;
-
- typedef sessionSet::iterator sessionIterator;
- sessionIterator sessionsBegin(void);
- sessionIterator sessionsEnd(void);
-
- sessionState *findSession(const std::string &handle);
- sessionState *findSessionBeingCreatedByURI(const std::string &uri);
- sessionState *findSession(const LLUUID &participant_id);
- sessionState *findSessionByCreateID(const std::string &create_id);
-
- sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null);
- void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null);
- void setSessionURI(sessionState *session, const std::string &uri);
- void deleteSession(sessionState *session);
- void deleteAllSessions(void);
-
- void verifySessionState(void);
-
- void joinedAudioSession(sessionState *session);
- void leftAudioSession(sessionState *session);
-
- // This is called in several places where the session _may_ need to be deleted.
- // It contains logic for whether to delete the session or keep it around.
- void reapSession(sessionState *session);
-
- // Returns true if the session seems to indicate we've moved to a region on a different voice server
- bool sessionNeedsRelog(sessionState *session);
-
- struct buddyListEntry
- {
- buddyListEntry(const std::string &uri);
- std::string mURI;
- std::string mDisplayName;
- LLUUID mUUID;
- bool mOnlineSL;
- bool mOnlineSLim;
- bool mCanSeeMeOnline;
- bool mHasBlockListEntry;
- bool mHasAutoAcceptListEntry;
- bool mNameResolved;
- bool mInSLFriends;
- bool mInVivoxBuddies;
- bool mNeedsNameUpdate;
- };
-
- typedef std::map<const std::string*, buddyListEntry*, stringMapComparitor> buddyListMap;
-
- // This should be called when parsing a buddy list entry sent by SLVoice.
- void processBuddyListEntry(const std::string &uri, const std::string &displayName);
-
- buddyListEntry *addBuddy(const std::string &uri);
- buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName);
- buddyListEntry *findBuddy(const std::string &uri);
- buddyListEntry *findBuddy(const LLUUID &id);
- buddyListEntry *findBuddyByDisplayName(const std::string &name);
- void deleteBuddy(const std::string &uri);
- void deleteAllBuddies(void);
-
- void deleteAllBlockRules(void);
- void addBlockRule(const std::string &blockMask, const std::string &presenceOnly);
- void deleteAllAutoAcceptRules(void);
- void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy);
- void accountListBlockRulesResponse(int statusCode, const std::string &statusString);
- void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);
-
- /////////////////////////////
- // session control messages
- void connectorCreate();
- void connectorShutdown();
-
- void requestVoiceAccountProvision(S32 retries = 3);
- void userAuthorized(const std::string& user_id,
- 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 sessionGroupTerminateSendMessage(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 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 avatarNameResolved(const LLUUID &id, const std::string &name);
-
- typedef std::vector<std::string> deviceList;
-
- deviceList *getCaptureDevices();
- deviceList *getRenderDevices();
-
- void setNonSpatialChannel(
- const std::string &uri,
- const std::string &credentials);
- void setSpatialChannel(
- const std::string &uri,
- const std::string &credentials);
- // 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();
+ std::string serverType;
+ std::string serverVersion;
+};
- // Returns the URI of the current channel, or an empty string if not currently in a channel.
- // NOTE that it will return an empty string if it's in the process of joining a channel.
- std::string getCurrentChannel();
-
- // returns true iff the user is currently in a proximal (local spatial) channel.
- // Note that gestures should only fire if this returns true.
- bool inProximalChannel();
+//////////////////////////////////
+/// @class LLVoiceModuleInterface
+/// @brief Voice module interface
+///
+/// Voice modules should provide an implementation for this interface.
+/////////////////////////////////
- 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);
-
- // Returns true if the indicated participant is really an SL avatar.
- // This should be used to control the state of the "profile" button.
- // Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls.
- bool isParticipantAvatar(const LLUUID &id);
-
- // Returns true if calling back the session URI after the session has closed is possible.
- // Currently this will be false only for PSTN P2P calls.
- // NOTE: this will return true if the session can't be found.
- bool isSessionCallBackPossible(const LLUUID &session_id);
-
- // Returns true if the session can accepte text IM's.
- // Currently this will be false only for PSTN P2P calls.
- // NOTE: this will return true if the session can't be found.
- bool isSessionTextIMPossible(const LLUUID &session_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
- 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, //
- stateJoiningSession, // waiting for session handle
- stateSessionJoined, // session handle received
- stateRunning, // in session, steady state
- stateLeavingSession, // waiting for terminate session response
- stateSessionTerminated, // waiting for terminate session response
-
- stateLoggingOut, // waiting for logout response
- stateLoggedOut, // logout response received
- stateConnectorStopping, // waiting for connector stop
- stateConnectorStopped, // connector stop received
-
- // We go to this state if the login fails because the account needs to be provisioned.
-
- // error states. No way to recover from these yet.
- stateConnectorFailed,
- stateConnectorFailedWaiting,
- stateLoginFailed,
- stateLoginFailedWaiting,
- stateJoinSessionFailed,
- stateJoinSessionFailedWaiting,
-
- stateJail // Go here when all else has failed. Nothing will be retried, we're done.
- };
-
- state mState;
- bool mSessionTerminateRequested;
- bool mRelogRequested;
-
- void setState(state inState);
- state getState(void) { return mState; };
- static std::string state2string(state inState);
-
- void stateMachine();
- static void idle(void *user_data);
-
- LLHost mDaemonHost;
- LLSocket::ptr_t mSocket;
- bool mConnected;
-
- void closeSocket(void);
-
- LLPumpIO *mPump;
- friend class LLVivoxProtocolParser;
-
- std::string mAccountName;
- std::string mAccountPassword;
- std::string mAccountDisplayName;
- std::string mAccountFirstName;
- std::string mAccountLastName;
-
- bool mTuningMode;
- float mTuningEnergy;
- std::string mTuningAudioFile;
- int mTuningMicVolume;
- bool mTuningMicVolumeDirty;
- int mTuningSpeakerVolume;
- bool mTuningSpeakerVolumeDirty;
- state mTuningExitState; // state to return to when we leave tuning mode.
-
- std::string mSpatialSessionURI;
- std::string mSpatialSessionCredentials;
+class LLVoiceModuleInterface
+{
+public:
+ LLVoiceModuleInterface() {}
+ virtual ~LLVoiceModuleInterface() {}
+
+ virtual void init(LLPumpIO *pump)=0; // Call this once at application startup (creates connector)
+ virtual void terminate()=0; // Call this to clean up during shutdown
+
+ virtual void updateSettings()=0; // call after loading settings and whenever they change
+
+ virtual const LLVoiceVersionInfo& getVersion()=0;
+
+ /////////////////////
+ /// @name Tuning
+ //@{
+ virtual void tuningStart()=0;
+ virtual void tuningStop()=0;
+ virtual bool inTuningMode()=0;
+
+ virtual void tuningSetMicVolume(float volume)=0;
+ virtual void tuningSetSpeakerVolume(float volume)=0;
+ virtual float tuningGetEnergy(void)=0;
+ //@}
+
+ /////////////////////
+ /// @name Devices
+ //@{
+ // This returns true when it's safe to bring up the "device settings" dialog in the prefs.
+ // i.e. when the daemon is running and connected, and the device lists are populated.
+ virtual bool deviceSettingsAvailable()=0;
+
+ // Requery the vivox daemon for the current list of input/output devices.
+ // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed
+ // (use this if you want to know when it's done).
+ // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim.
+ virtual void refreshDeviceLists(bool clearCurrentList = true)=0;
+
+ virtual void setCaptureDevice(const std::string& name)=0;
+ virtual void setRenderDevice(const std::string& name)=0;
+
+ virtual LLVoiceDeviceList& getCaptureDevices()=0;
+ virtual LLVoiceDeviceList& getRenderDevices()=0;
+
+ virtual std::vector<LLUUID> getParticipantList(void)=0;
+ //@}
+
+ ////////////////////////////
+ /// @ name Channel stuff
+ //@{
+ // returns true iff the user is currently in a proximal (local spatial) channel.
+ // Note that gestures should only fire if this returns true.
+ virtual bool inProximalChannel()=0;
+
+ virtual void setNonSpatialChannel(const std::string &uri,
+ const std::string &credentials)=0;
+
+ virtual void setSpatialChannel(const std::string &uri,
+ const std::string &credentials)=0;
+
+ virtual void leaveNonSpatialChannel()=0;
+
+ virtual void leaveChannel(void)=0;
+
+ // Returns the URI of the current channel, or an empty string if not currently in a channel.
+ // NOTE that it will return an empty string if it's in the process of joining a channel.
+ virtual std::string getCurrentChannel()=0;
+ //@}
+
+
+ //////////////////////////
+ /// @name invitations
+ //@{
+ // start a voice channel with the specified user
+ virtual void callUser(const LLUUID &uuid)=0;
+ virtual bool answerInvite(std::string &channelHandle)=0;
+ virtual void declineInvite(std::string &channelHandle)=0;
+ //@}
+
+ /////////////////////////
+ /// @name Volume/gain
+ //@{
+ virtual void setVoiceVolume(F32 volume)=0;
+ virtual void setMicGain(F32 volume)=0;
+ //@}
+
+ /////////////////////////
+ /// @name enable disable voice and features
+ //@{
+ virtual bool voiceEnabled()=0;
+ virtual void setVoiceEnabled(bool enabled)=0;
+ virtual void setLipSyncEnabled(BOOL enabled)=0;
+ virtual BOOL lipSyncEnabled()=0;
+ virtual void setMuteMic(bool muted)=0; // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state.
+ //@}
+
+ ////////////////////////
+ /// @name PTT
+ //@{
+ virtual void setUserPTTState(bool ptt)=0;
+ virtual bool getUserPTTState()=0;
+ virtual void setUsePTT(bool usePTT)=0;
+ virtual void setPTTIsToggle(bool PTTIsToggle)=0;
+ virtual bool getPTTIsToggle()=0;
+ virtual void toggleUserPTTState(void)=0;
+ virtual void inputUserControlState(bool down)=0; // interpret any sort of up-down mic-open control input according to ptt-toggle prefs
+
+ virtual void keyDown(KEY key, MASK mask)=0;
+ virtual void keyUp(KEY key, MASK mask)=0;
+ virtual void middleMouseState(bool down)=0;
+ //@}
+
+ //////////////////////////
+ /// @name nearby speaker accessors
+ //@{
+
+
+ virtual BOOL getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar
+ virtual std::string getDisplayName(const LLUUID& id)=0;
+ virtual BOOL isOnlineSIP(const LLUUID &id)=0;
+ virtual BOOL isParticipantAvatar(const LLUUID &id)=0;
+ virtual BOOL getIsSpeaking(const LLUUID& id)=0;
+ virtual BOOL getIsModeratorMuted(const LLUUID& id)=0;
+ virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
+ virtual BOOL getOnMuteList(const LLUUID& id)=0;
+ virtual F32 getUserVolume(const LLUUID& id)=0;
+ virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal)
+ //@}
+
+ //////////////////////////
+ /// @name text chat
+ //@{
+ virtual BOOL isSessionTextIMPossible(const LLUUID& id)=0;
+ virtual BOOL isSessionCallBackPossible(const LLUUID& id)=0;
+ virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message)=0;
+ virtual void endUserIMSession(const LLUUID &uuid)=0;
+ //@}
+
+ // authorize the user
+ virtual void userAuthorized(const std::string& user_id,
+ const LLUUID &agentID)=0;
+
+ //////////////////////////////
+ /// @name Status notification
+ //@{
+ virtual void addObserver(LLVoiceClientStatusObserver* observer)=0;
+ virtual void removeObserver(LLVoiceClientStatusObserver* observer)=0;
+ virtual void addObserver(LLFriendObserver* observer)=0;
+ virtual void removeObserver(LLFriendObserver* observer)=0;
+ virtual void addObserver(LLVoiceClientParticipantObserver* observer)=0;
+ virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0;
+ //@}
+
+ virtual std::string sipURIFromID(const LLUUID &id)=0;
+ //@}
+
+};
- std::string mMainSessionGroupHandle; // handle of the "main" session group.
-
- std::string mChannelName; // Name of the channel to be looked up
- bool mAreaVoiceDisabled;
- 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
+class LLVoiceClient: public LLSingleton<LLVoiceClient>
+{
+ LOG_CLASS(LLVoiceClient);
+public:
+ LLVoiceClient();
+ ~LLVoiceClient();
-// 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
- int mNumberOfAliases;
- U32 mCommandCookie;
+ void init(LLPumpIO *pump); // Call this once at application startup (creates connector)
+ void terminate(); // Call this to clean up during shutdown
- std::string mVoiceAccountServerURI;
- std::string mVoiceSIPURIHostName;
-
- int mLoginRetryCount;
-
- 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;
+ const LLVoiceVersionInfo getVersion();
+
+static const F32 OVERDRIVEN_POWER_LEVEL;
- std::string mCaptureDevice;
- std::string mRenderDevice;
- bool mCaptureDeviceDirty;
- bool mRenderDeviceDirty;
-
- // 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 no_reconnect = false, bool is_p2p = false, std::string hash = "");
- void joinSession(sessionState *session);
-
-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);
+ void updateSettings(); // call after loading settings and whenever they change
- 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);
+ // tuning
+ void tuningStart();
+ void tuningStop();
+ bool inTuningMode();
- 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);
-
- bool mSpatialCoordsDirty;
-
- LLVector3d mCameraPosition;
- LLVector3d mCameraRequestedPosition;
- LLVector3 mCameraVelocity;
- LLMatrix3 mCameraRot;
-
- LLVector3d mAvatarPosition;
- LLVector3 mAvatarVelocity;
- LLMatrix3 mAvatarRot;
-
- bool mPTTDirty;
- bool mPTT;
-
- bool mUsePTT;
- bool mPTTIsMiddleMouse;
- KEY mPTTKey;
- bool mPTTIsToggle;
- bool mUserPTTState;
- bool mMuteMic;
+ void tuningSetMicVolume(float volume);
+ void tuningSetSpeakerVolume(float volume);
+ float tuningGetEnergy(void);
- // Set to true when the friends list is known to have changed.
- bool mFriendsListDirty;
-
- enum
- {
- earLocCamera = 0, // ear at camera
- earLocAvatar, // ear at avatar
- earLocMixed // ear at avatar location/camera direction
- };
-
- S32 mEarLocation;
+ // devices
+
+ // This returns true when it's safe to bring up the "device settings" dialog in the prefs.
+ // i.e. when the daemon is running and connected, and the device lists are populated.
+ bool deviceSettingsAvailable();
- bool mSpeakerVolumeDirty;
- bool mSpeakerMuteDirty;
- int mSpeakerVolume;
+ // Requery the vivox daemon for the current list of input/output devices.
+ // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed
+ // (use this if you want to know when it's done).
+ // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim.
+ void refreshDeviceLists(bool clearCurrentList = true);
- int mMicVolume;
- bool mMicVolumeDirty;
-
- bool mVoiceEnabled;
- bool mWriteInProgress;
- std::string mWriteString;
-
- LLTimer mUpdateTimer;
-
- BOOL mLipSyncEnabled;
+ void setCaptureDevice(const std::string& name);
+ void setRenderDevice(const std::string& name);
- std::string mAPIVersion;
+ const LLVoiceDeviceList& getCaptureDevices();
+ const LLVoiceDeviceList& getRenderDevices();
- typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t;
- observer_set_t mParticipantObservers;
+ ////////////////////////////
+ // Channel stuff
+ //
+
+ // returns true iff the user is currently in a proximal (local spatial) channel.
+ // Note that gestures should only fire if this returns true.
+ bool inProximalChannel();
+ void setNonSpatialChannel(
+ const std::string &uri,
+ const std::string &credentials);
+ void setSpatialChannel(
+ const std::string &uri,
+ const std::string &credentials);
+ void leaveNonSpatialChannel();
+
+ // Returns the URI of the current channel, or an empty string if not currently in a channel.
+ // NOTE that it will return an empty string if it's in the process of joining a channel.
+ std::string getCurrentChannel();
+ // start a voice channel with the specified user
+ void callUser(const LLUUID &uuid);
+ bool answerInvite(std::string &channelHandle);
+ void declineInvite(std::string &channelHandle);
+ void leaveChannel(void); // call this on logout or teleport begin
+
+
+ /////////////////////////////
+ // Sending updates of current state
+
- void notifyParticipantObservers();
+ 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)
+ bool voiceEnabled();
+ void setLipSyncEnabled(BOOL enabled);
+ void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state.
+ void setUserPTTState(bool ptt);
+ bool getUserPTTState();
+ void toggleUserPTTState(void);
+ void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs
+ void setVoiceEnabled(bool enabled);
+
+ void setUsePTT(bool usePTT);
+ void setPTTIsToggle(bool PTTIsToggle);
+ bool getPTTIsToggle();
+
+ BOOL lipSyncEnabled();
+
+ // PTT key triggering
+ void keyDown(KEY key, MASK mask);
+ void keyUp(KEY key, MASK mask);
+ void middleMouseState(bool down);
+
+
+ /////////////////////////////
+ // Accessors for data related to nearby speakers
+ BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar
+ std::string getDisplayName(const LLUUID& id);
+ BOOL isOnlineSIP(const LLUUID &id);
+ BOOL isParticipantAvatar(const LLUUID &id);
+ 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 getOnMuteList(const LLUUID& id);
+ F32 getUserVolume(const LLUUID& id);
+
+ /////////////////////////////
+ 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.
+ std::vector<LLUUID> getParticipantList(void);
+
+ //////////////////////////
+ /// @name text chat
+ //@{
+ BOOL isSessionTextIMPossible(const LLUUID& id);
+ BOOL isSessionCallBackPossible(const LLUUID& id);
+ BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message);
+ void endUserIMSession(const LLUUID &uuid);
+ //@}
+
- typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t;
- status_observer_set_t mStatusObservers;
+ void userAuthorized(const std::string& user_id,
+ const LLUUID &agentID);
+
+ void addObserver(LLVoiceClientStatusObserver* observer);
+ void removeObserver(LLVoiceClientStatusObserver* observer);
+ void addObserver(LLFriendObserver* observer);
+ void removeObserver(LLFriendObserver* observer);
+ void addObserver(LLVoiceClientParticipantObserver* observer);
+ void removeObserver(LLVoiceClientParticipantObserver* observer);
+
+ std::string sipURIFromID(const LLUUID &id);
- void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status);
-
- typedef std::set<LLFriendObserver*> friend_observer_set_t;
- friend_observer_set_t mFriendObservers;
- void notifyFriendObservers();
+protected:
+ LLVoiceModuleInterface* mVoiceModule;
+ LLPumpIO *m_servicePump;
+
};
-extern LLVoiceClient *gVoiceClient;
#endif //LL_VOICE_CLIENT_H
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
new file mode 100644
index 0000000000..41c20597b1
--- /dev/null
+++ b/indra/newview/llvoicevivox.cpp
@@ -0,0 +1,6900 @@
+ /**
+ * @file LLVivoxVoiceClient.cpp
+ * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewergpl$
+ *
+ * Copyright (c) 2001-2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llvoicevivox.h"
+
+#include <boost/tokenizer.hpp>
+
+#include "llsdutil.h"
+
+#include "llvoavatarself.h"
+#include "llbufferstream.h"
+#include "llfile.h"
+#ifdef LL_STANDALONE
+# include "expat.h"
+#else
+# include "expat/expat.h"
+#endif
+#include "llcallbacklist.h"
+#include "llviewerregion.h"
+#include "llviewernetwork.h" // for gGridChoice
+#include "llbase64.h"
+#include "llviewercontrol.h"
+#include "llkeyboard.h"
+#include "llappviewer.h" // for gDisconnected, gDisableVoice
+#include "llmutelist.h" // to check for muted avatars
+#include "llagent.h"
+#include "llcachename.h"
+#include "llimview.h" // for LLIMMgr
+#include "llparcel.h"
+#include "llviewerparcelmgr.h"
+#include "llfirstuse.h"
+#include "llviewerwindow.h"
+#include "llviewercamera.h"
+
+#include "llfloaterfriends.h" //VIVOX, inorder to refresh communicate panel
+#include "llfloaterchat.h" // for LLFloaterChat::addChat()
+#include "llviewernetwork.h"
+#include "llnotificationsutil.h"
+
+// for base64 decoding
+#include "apr_base64.h"
+
+// for SHA1 hash
+#include "apr_sha1.h"
+
+// for MD5 hash
+#include "llmd5.h"
+
+#define USE_SESSION_GROUPS 0
+
+const F32 SPEAKING_TIMEOUT = 1.f;
+
+static const std::string VOICE_SERVER_TYPE = "Vivox";
+
+// Don't retry connecting to the daemon more frequently than this:
+const F32 CONNECT_THROTTLE_SECONDS = 1.0f;
+
+// Don't send positional updates more frequently than this:
+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 to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70
+ return 30 + (int)(volume * 20.0f);
+}
+
+static int scale_speaker_volume(float volume)
+{
+ // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
+ // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70
+ return 30 + (int)(volume * 40.0f);
+
+}
+
+class LLVivoxVoiceAccountProvisionResponder :
+ public LLHTTPClient::Responder
+{
+public:
+ LLVivoxVoiceAccountProvisionResponder(int retries)
+ {
+ mRetries = retries;
+ }
+
+ virtual void error(U32 status, const std::string& reason)
+ {
+ if ( mRetries > 0 )
+ {
+ LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
+ LLVivoxVoiceClient::getInstance()->requestVoiceAccountProvision(
+ mRetries - 1);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
+ LLVivoxVoiceClient::getInstance()->giveUp();
+ }
+ }
+
+ virtual void result(const LLSD& content)
+ {
+
+ 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();
+
+ LLVivoxVoiceClient::getInstance()->login(
+ content["username"].asString(),
+ content["password"].asString(),
+ voice_sip_uri_hostname,
+ voice_account_server_uri);
+
+ }
+
+private:
+ int mRetries;
+};
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver
+{
+ /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();}
+};
+
+class LLVivoxVoiceClientFriendsObserver : public LLFriendObserver
+{
+public:
+ /* virtual */ void changed(U32 mask) { LLVivoxVoiceClient::getInstance()->updateFriends(mask);}
+};
+
+static LLVivoxVoiceClientMuteListObserver mutelist_listener;
+static bool sMuteListListener_listening = false;
+
+static LLVivoxVoiceClientFriendsObserver *friendslist_listener = NULL;
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+class LLVivoxVoiceClientCapResponder : public LLHTTPClient::Responder
+{
+public:
+ LLVivoxVoiceClientCapResponder(void){};
+
+ virtual void error(U32 status, const std::string& reason); // called with bad status codes
+ virtual void result(const LLSD& content);
+
+private:
+};
+
+void LLVivoxVoiceClientCapResponder::error(U32 status, const std::string& reason)
+{
+ LL_WARNS("Voice") << "LLVivoxVoiceClientCapResponder::error("
+ << status << ": " << reason << ")"
+ << LL_ENDL;
+}
+
+void LLVivoxVoiceClientCapResponder::result(const LLSD& content)
+{
+ LLSD::map_const_iterator iter;
+
+ LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;
+
+ if ( content.has("voice_credentials") )
+ {
+ LLSD voice_credentials = content["voice_credentials"];
+ std::string uri;
+ std::string credentials;
+
+ if ( voice_credentials.has("channel_uri") )
+ {
+ uri = voice_credentials["channel_uri"].asString();
+ }
+ if ( voice_credentials.has("channel_credentials") )
+ {
+ credentials =
+ voice_credentials["channel_credentials"].asString();
+ }
+
+ LLVivoxVoiceClient::getInstance()->setSpatialChannel(uri, credentials);
+ }
+}
+
+
+
+#if LL_WINDOWS
+static HANDLE sGatewayHandle = 0;
+
+static bool isGatewayRunning()
+{
+ bool result = false;
+ if(sGatewayHandle != 0)
+ {
+ DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0);
+ if(waitresult != WAIT_OBJECT_0)
+ {
+ result = true;
+ }
+ }
+ return result;
+}
+static void killGateway()
+{
+ if(sGatewayHandle != 0)
+ {
+ TerminateProcess(sGatewayHandle,0);
+ }
+}
+
+#else // Mac and linux
+
+static pid_t sGatewayPID = 0;
+static bool isGatewayRunning()
+{
+ bool result = false;
+ if(sGatewayPID != 0)
+ {
+ // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists.
+ if(kill(sGatewayPID, 0) == 0)
+ {
+ result = true;
+ }
+ }
+ return result;
+}
+
+static void killGateway()
+{
+ if(sGatewayPID != 0)
+ {
+ kill(sGatewayPID, SIGTERM);
+ }
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+LLVivoxVoiceClient::LLVivoxVoiceClient() :
+ mState(stateDisabled),
+ mSessionTerminateRequested(false),
+ mRelogRequested(false),
+ mConnected(false),
+ mPump(NULL),
+
+ mTuningMode(false),
+ mTuningEnergy(0.0f),
+ mTuningMicVolume(0),
+ mTuningMicVolumeDirty(true),
+ mTuningSpeakerVolume(0),
+ mTuningSpeakerVolumeDirty(true),
+ mTuningExitState(stateDisabled),
+
+ mAreaVoiceDisabled(false),
+ mAudioSession(NULL),
+ mAudioSessionChanged(false),
+ mNextAudioSession(NULL),
+
+ mCurrentParcelLocalID(0),
+ mNumberOfAliases(0),
+ mCommandCookie(0),
+ mLoginRetryCount(0),
+
+ mBuddyListMapPopulated(false),
+ mBlockRulesListReceived(false),
+ mAutoAcceptRulesListReceived(false),
+ mCaptureDeviceDirty(false),
+ mRenderDeviceDirty(false),
+ mSpatialCoordsDirty(false),
+
+ mPTTDirty(true),
+ mPTT(true),
+ mUsePTT(true),
+ mPTTIsMiddleMouse(false),
+ mPTTKey(0),
+ mPTTIsToggle(false),
+ mUserPTTState(false),
+ mMuteMic(false),
+ mFriendsListDirty(true),
+
+ mEarLocation(0),
+ mSpeakerVolumeDirty(true),
+ mSpeakerMuteDirty(true),
+ mMicVolume(0),
+ mMicVolumeDirty(true),
+
+ mVoiceEnabled(false),
+ mWriteInProgress(false),
+
+ mLipSyncEnabled(false)
+
+
+
+{
+ mSpeakerVolume = scale_speaker_volume(0);
+
+ mVoiceVersion.serverVersion = "";
+ mVoiceVersion.serverType = VOICE_SERVER_TYPE;
+
+ // gMuteListp isn't set up at this point, so we defer this until later.
+// gMuteListp->addObserver(&mutelist_listener);
+
+
+#if LL_DARWIN || LL_LINUX || LL_SOLARIS
+ // 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?
+ signal(SIGPIPE, SIG_IGN);
+
+ // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes.
+ // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that.
+ signal(SIGCHLD, SIG_IGN);
+#endif
+
+ // set up state machine
+ setState(stateDisabled);
+
+ gIdleCallbacks.addFunction(idle, this);
+}
+
+//---------------------------------------------------
+
+LLVivoxVoiceClient::~LLVivoxVoiceClient()
+{
+}
+
+//----------------------------------------------
+
+void LLVivoxVoiceClient::init(LLPumpIO *pump)
+{
+ // constructor will set up LLVoiceClient::getInstance()
+ LLVivoxVoiceClient::getInstance()->mPump = pump;
+}
+
+void LLVivoxVoiceClient::terminate()
+{
+
+// leaveAudioSession();
+ 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);
+ connectorShutdown();
+ closeSocket(); // Need to do this now -- bad things happen if the destructor does it later.
+
+ // This will do unpleasant things on windows.
+// killGateway();
+
+
+
+}
+
+const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion()
+{
+ return mVoiceVersion;
+}
+
+//---------------------------------------------------
+
+void LLVivoxVoiceClient::updateSettings()
+{
+ setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat"));
+ setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled"));
+ std::string keyString = gSavedSettings.getString("PushToTalkButton");
+ setPTTKey(keyString);
+ setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle"));
+ setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
+
+ std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
+ setCaptureDevice(inputDevice);
+ std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
+ setRenderDevice(outputDevice);
+ F32 mic_level = gSavedSettings.getF32("AudioLevelMic");
+ setMicGain(mic_level);
+ setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled"));
+}
+
+/////////////////////////////
+// utility functions
+
+bool LLVivoxVoiceClient::writeString(const std::string &str)
+{
+ bool result = false;
+ if(mConnected)
+ {
+ apr_status_t err;
+ apr_size_t size = (apr_size_t)str.size();
+ apr_size_t written = size;
+
+ //MARK: Turn this on to log outgoing XML
+// LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
+
+ // check return code - sockets will fail (broken, etc.)
+ err = apr_socket_send(
+ mSocket->getSocket(),
+ (const char*)str.data(),
+ &written);
+
+ if(err == 0)
+ {
+ // Success.
+ result = true;
+ }
+ // 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))
+// {
+// //
+// }
+ else
+ {
+ // Assume any socket error means something bad. For now, just close the socket.
+ char buf[MAX_STRING];
+ LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL;
+ daemonDied();
+ }
+ }
+
+ return result;
+}
+
+
+/////////////////////////////
+// session control messages
+void LLVivoxVoiceClient::connectorCreate()
+{
+ std::ostringstream stream;
+ 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";
+ }
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">"
+ << "<ClientName>V2 SDK</ClientName>"
+ << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>"
+ << "<Mode>Normal</Mode>"
+ << "<Logging>"
+ << "<Folder>" << logpath << "</Folder>"
+ << "<FileNamePrefix>Connector</FileNamePrefix>"
+ << "<FileNameSuffix>.log</FileNameSuffix>"
+ << "<LogLevel>" << loglevel << "</LogLevel>"
+ << "</Logging>"
+ << "<Application>SecondLifeViewer.1</Application>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::connectorShutdown()
+{
+ setState(stateConnectorStopping);
+
+ if(!mConnectorHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ mConnectorHandle.clear();
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID)
+{
+
+ mAccountDisplayName = user_id;
+
+ LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL;
+
+ mAccountName = nameFromID(agentID);
+}
+
+void LLVivoxVoiceClient::requestVoiceAccountProvision(S32 retries)
+{
+ if ( gAgent.getRegion() && mVoiceEnabled )
+ {
+ std::string url =
+ gAgent.getRegion()->getCapability(
+ "ProvisionVoiceAccountRequest");
+
+ if ( url == "" ) return;
+
+ LLHTTPClient::post(
+ url,
+ LLSD(),
+ new LLVivoxVoiceAccountProvisionResponder(retries));
+ }
+}
+
+void LLVivoxVoiceClient::login(
+ const std::string& account_name,
+ const std::string& password,
+ const std::string& voice_sip_uri_hostname,
+ const std::string& voice_account_server_uri)
+{
+ mVoiceSIPURIHostName = voice_sip_uri_hostname;
+ mVoiceAccountServerURI = voice_account_server_uri;
+
+ if(!mAccountHandle.empty())
+ {
+ // Already logged in.
+ LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL;
+
+ // Don't process another login.
+ return;
+ }
+ else if ( account_name != mAccountName )
+ {
+ //TODO: error?
+ 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(LLGridManager::getInstance()->isInProductionGrid())
+ {
+ // 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 LLVivoxVoiceClient::idle(void* user_data)
+{
+ LLVivoxVoiceClient* self = (LLVivoxVoiceClient*)user_data;
+ self->stateMachine();
+}
+
+std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState)
+{
+ std::string result = "UNKNOWN";
+
+ // Prevent copy-paste errors when updating this list...
+#define CASE(x) case x: result = #x; break
+
+ 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);
+ CASE(stateLoginRetry);
+ CASE(stateLoginRetryWait);
+ CASE(stateNeedsLogin);
+ CASE(stateLoggingIn);
+ CASE(stateLoggedIn);
+ CASE(stateCreatingSessionGroup);
+ CASE(stateNoChannel);
+ CASE(stateJoiningSession);
+ CASE(stateSessionJoined);
+ CASE(stateRunning);
+ CASE(stateLeavingSession);
+ CASE(stateSessionTerminated);
+ CASE(stateLoggingOut);
+ CASE(stateLoggedOut);
+ CASE(stateConnectorStopping);
+ CASE(stateConnectorStopped);
+ CASE(stateConnectorFailed);
+ CASE(stateConnectorFailedWaiting);
+ CASE(stateLoginFailed);
+ CASE(stateLoginFailedWaiting);
+ CASE(stateJoinSessionFailed);
+ CASE(stateJoinSessionFailedWaiting);
+ CASE(stateJail);
+ }
+
+#undef CASE
+
+ return result;
+}
+
+
+
+void LLVivoxVoiceClient::setState(state inState)
+{
+ LL_DEBUGS("Voice") << "entering state " << state2string(inState) << LL_ENDL;
+
+ mState = inState;
+}
+
+void LLVivoxVoiceClient::stateMachine()
+{
+ if(gDisconnected)
+ {
+ // The viewer has been disconnected from the sim. Disable voice.
+ setVoiceEnabled(false);
+ }
+
+ if(mVoiceEnabled)
+ {
+ updatePosition();
+ }
+ else if(mTuningMode)
+ {
+ // Tuning mode is special -- it needs to launch SLVoice even if voice is disabled.
+ }
+ else
+ {
+ if((getState() != stateDisabled) && (getState() != stateDisableCleanup))
+ {
+ // User turned off voice support. Send the cleanup messages, close the socket, and reset.
+ if(!mConnected)
+ {
+ // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill.
+ LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL;
+ killGateway();
+ }
+
+ logout();
+ connectorShutdown();
+
+ setState(stateDisableCleanup);
+ }
+ }
+
+ // Check for parcel boundary crossing
+ {
+ LLViewerRegion *region = gAgent.getRegion();
+ LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
+
+ if(region && parcel)
+ {
+ S32 parcelLocalID = parcel->getLocalID();
+ std::string regionName = region->getName();
+ std::string capURI = region->getCapability("ParcelVoiceInfoRequest");
+
+// LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << 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.
+ // If either is empty, wait for the next time around.
+ if(!regionName.empty())
+ {
+ if(!capURI.empty())
+ {
+ if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName))
+ {
+ // We have changed parcels. Initiate a parcel channel lookup.
+ mCurrentParcelLocalID = parcelLocalID;
+ mCurrentRegionName = regionName;
+
+ parcelChanged();
+ }
+ }
+ else
+ {
+ LL_WARNS_ONCE("Voice") << "region doesn't have ParcelVoiceInfoRequest capability. This is normal for a short time after teleporting, but bad if it persists for very long." << LL_ENDL;
+ }
+ }
+ }
+ }
+
+ 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(mTuningMode || (mVoiceEnabled && !mAccountName.empty()))
+ {
+ setState(stateStart);
+ }
+ break;
+
+ //MARK: stateStart
+ case stateStart:
+ if(gSavedSettings.getBOOL("CmdLineDisableVoice"))
+ {
+ // Voice is locked out, we must not launch the vivox daemon.
+ setState(stateJail);
+ }
+ else if(!isGatewayRunning())
+ {
+ if(true)
+ {
+ // Launch the voice daemon
+
+ // *FIX:Mani - Using the executable dir instead
+ // of mAppRODataDir, the working directory from which the app
+ // is launched.
+ //std::string exe_path = gDirUtilp->getAppRODataDir();
+ std::string exe_path = gDirUtilp->getExecutableDir();
+ exe_path += gDirUtilp->getDirDelimiter();
+#if LL_WINDOWS
+ exe_path += "SLVoice.exe";
+#elif LL_DARWIN
+ exe_path += "../Resources/SLVoice";
+#else
+ exe_path += "SLVoice";
+#endif
+ // See if the vivox executable exists
+ llstat s;
+ if(!LLFile::stat(exe_path, &s))
+ {
+ // vivox executable exists. Build the command line and launch the daemon.
+ // SLIM SDK: these arguments are no longer necessary.
+// std::string args = " -p tcp -h -c";
+ std::string args;
+ std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
+
+ if(loglevel.empty())
+ {
+ loglevel = "-1"; // turn logging off completely
+ }
+
+ args += " -ll ";
+ args += loglevel;
+
+ LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL;
+
+#if LL_WINDOWS
+ PROCESS_INFORMATION pinfo;
+ STARTUPINFOW sinfo;
+
+ memset(&sinfo, 0, sizeof(sinfo));
+
+ std::string exe_dir = gDirUtilp->getExecutableDir();
+
+ llutf16string exe_path16 = utf8str_to_utf16str(exe_path);
+ llutf16string exe_dir16 = utf8str_to_utf16str(exe_dir);
+ llutf16string args16 = utf8str_to_utf16str(args);
+ // Create a writeable copy to keep Windows happy.
+ U16 *argscpy_16 = new U16[args16.size() + 1];
+ wcscpy_s(argscpy_16,args16.size()+1,args16.c_str());
+
+ if(!CreateProcessW(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo))
+ {
+// DWORD dwErr = GetLastError();
+ }
+ else
+ {
+ // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
+ // CloseHandle(pinfo.hProcess); // stops leaks - nothing else
+ sGatewayHandle = pinfo.hProcess;
+ CloseHandle(pinfo.hThread); // stops leaks - nothing else
+ }
+
+ delete[] argscpy_16;
+#else // LL_WINDOWS
+ // This should be the same for mac and linux
+ {
+ std::vector<std::string> arglist;
+ arglist.push_back(exe_path);
+
+ // Split the argument string into separate strings for each argument
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(" ");
+ tokenizer tokens(args, sep);
+ tokenizer::iterator token_iter;
+
+ for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
+ {
+ arglist.push_back(*token_iter);
+ }
+
+ // create an argv vector for the child process
+ char **fakeargv = new char*[arglist.size() + 1];
+ int i;
+ for(i=0; i < arglist.size(); i++)
+ fakeargv[i] = const_cast<char*>(arglist[i].c_str());
+
+ fakeargv[i] = NULL;
+
+ fflush(NULL); // flush all buffers before the child inherits them
+ pid_t id = vfork();
+ if(id == 0)
+ {
+ // child
+ execv(exe_path.c_str(), fakeargv);
+
+ // If we reach this point, the exec failed.
+ // Use _exit() instead of exit() per the vfork man page.
+ _exit(0);
+ }
+
+ // parent
+ delete[] fakeargv;
+ sGatewayPID = id;
+ }
+#endif // LL_WINDOWS
+ mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort"));
+ }
+ else
+ {
+ 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:44125
+ // and put that host's IP address here.
+ mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort"));
+ }
+
+ mUpdateTimer.start();
+ mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
+
+ setState(stateDaemonLaunched);
+
+ // 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:
+ if(mUpdateTimer.hasExpired())
+ {
+ LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL;
+
+ mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
+
+ if(!mSocket)
+ {
+ mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
+ }
+
+ mConnected = mSocket->blockingConnect(mDaemonHost);
+ if(mConnected)
+ {
+ setState(stateConnecting);
+ }
+ else
+ {
+ // If the connect failed, the socket may have been put into a bad state. Delete it.
+ closeSocket();
+ }
+ }
+ break;
+
+ //MARK: stateConnecting
+ case stateConnecting:
+ // Can't do this until we have the pump available.
+ if(mPump)
+ {
+ // MBW -- Note to self: pumps and pipes examples in
+ // indra/test/io.cpp
+ // indra/test/llpipeutil.{cpp|h}
+
+ // Attach the pumps and pipes
+
+ LLPumpIO::chain_t readChain;
+
+ readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket)));
+ readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser()));
+
+ mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);
+
+ setState(stateConnected);
+ }
+
+ break;
+
+ //MARK: stateConnected
+ case stateConnected:
+ // Initial devices query
+ getCaptureDevicesSendMessage();
+ getRenderDevicesSendMessage();
+
+ mLoginRetryCount = 0;
+
+ setState(stateIdle);
+ break;
+
+ //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 = stateIdle;
+ setState(stateMicTuningStart);
+ }
+ else if(!mVoiceEnabled)
+ {
+ // We never started up the connector. This will shut down the daemon.
+ setState(stateConnectorStopped);
+ }
+ else if(!mAccountName.empty())
+ {
+ LLViewerRegion *region = gAgent.getRegion();
+
+ if(region)
+ {
+ if ( region->getCapability("ProvisionVoiceAccountRequest") != "" )
+ {
+ if ( mAccountPassword.empty() )
+ {
+ requestVoiceAccountProvision();
+ }
+ setState(stateConnectorStart);
+ }
+ else
+ {
+ LL_WARNS_ONCE("Voice") << "region doesn't have ProvisionVoiceAccountRequest capability!" << LL_ENDL;
+ }
+ }
+ }
+ break;
+
+ //MARK: stateMicTuningStart
+ case stateMicTuningStart:
+ if(mUpdateTimer.hasExpired())
+ {
+ if(mCaptureDeviceDirty || mRenderDeviceDirty)
+ {
+ // These can't be changed while in tuning mode. Set them before starting.
+ std::ostringstream stream;
+
+ buildSetCaptureDevice(stream);
+ buildSetRenderDevice(stream);
+
+ if(!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+
+ // This will come around again in the same state and start the capture, after the timer expires.
+ mUpdateTimer.start();
+ mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
+ }
+ else
+ {
+ // duration parameter is currently unused, per Mike S.
+ tuningCaptureStartSendMessage(10000);
+
+ setState(stateMicTuningRunning);
+ }
+ }
+
+ break;
+
+ //MARK: stateMicTuningRunning
+ case stateMicTuningRunning:
+ if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty)
+ {
+ // All of these conditions make us leave tuning mode.
+ setState(stateMicTuningStop);
+ }
+ else
+ {
+ // process mic/speaker volume changes
+ if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty)
+ {
+ std::ostringstream stream;
+
+ if(mTuningMicVolumeDirty)
+ {
+ LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">"
+ << "<Level>" << mTuningMicVolume << "</Level>"
+ << "</Request>\n\n\n";
+ }
+
+ if(mTuningSpeakerVolumeDirty)
+ {
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">"
+ << "<Level>" << mTuningSpeakerVolume << "</Level>"
+ << "</Request>\n\n\n";
+ }
+
+ mTuningMicVolumeDirty = false;
+ mTuningSpeakerVolumeDirty = false;
+
+ if(!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+ }
+ }
+ break;
+
+ //MARK: stateMicTuningStop
+ case stateMicTuningStop:
+ {
+ // transition out of mic tuning
+ tuningCaptureStopSendMessage();
+
+ setState(mTuningExitState);
+
+ // if we exited just to change devices, this will keep us from re-entering too fast.
+ mUpdateTimer.start();
+ mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
+
+ }
+ 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)
+ {
+ // First retry -- display a message to the user
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY);
+ }
+
+ mLoginRetryCount++;
+
+ if(mLoginRetryCount > MAX_LOGIN_RETRIES)
+ {
+ LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL;
+ setState(stateLoginFailed);
+ LLSD args;
+ std::stringstream errs;
+ errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000";
+ args["HOSTID"] = errs.str();
+ if (LLGridManager::getInstance()->isSystemGrid())
+ {
+ LLNotificationsUtil::add("NoVoiceConnect", args);
+ }
+ else
+ {
+ LLNotificationsUtil::add("NoVoiceConnect-GIAB", args);
+ }
+ }
+ else
+ {
+ LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL;
+ mUpdateTimer.start();
+ mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS);
+ setState(stateLoginRetryWait);
+ }
+ break;
+
+ //MARK: stateLoginRetryWait
+ case stateLoginRetryWait:
+ if(mUpdateTimer.hasExpired())
+ {
+ setState(stateNeedsLogin);
+ }
+ break;
+
+ //MARK: stateNeedsLogin
+ case stateNeedsLogin:
+ if(!mAccountPassword.empty())
+ {
+ setState(stateLoggingIn);
+ loginSendMessage();
+ }
+ break;
+
+ //MARK: stateLoggingIn
+ case stateLoggingIn: // waiting for account handle
+ // loginResponse() will transition from here to stateLoggedIn.
+ break;
+
+ //MARK: stateLoggedIn
+ case stateLoggedIn: // account handle received
+
+ 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))
+ {
+ LLMuteList::getInstance()->addObserver(&mutelist_listener);
+ sMuteListListener_listening = true;
+ }
+
+ // Set up the friends list observer if it hasn't been set up already.
+ if(friendslist_listener == NULL)
+ {
+ friendslist_listener = new LLVivoxVoiceClientFriendsObserver;
+ LLAvatarTracker::instance().addObserver(friendslist_listener);
+ }
+
+ // Set the initial state of mic mute, local speaker volume, etc.
+ {
+ std::ostringstream stream;
+
+ buildLocalAudioUpdates(stream);
+
+ if(!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+ }
+
+#if USE_SESSION_GROUPS
+ // create the main session group
+ sessionGroupCreateSendMessage();
+
+ setState(stateCreatingSessionGroup);
+#else
+ // Not using session groups -- skip the stateCreatingSessionGroup state.
+ setState(stateNoChannel);
+
+ // Initial kick-off of channel lookup logic
+ parcelChanged();
+#endif
+ break;
+
+ //MARK: stateCreatingSessionGroup
+ case stateCreatingSessionGroup:
+ if(mSessionTerminateRequested || !mVoiceEnabled)
+ {
+ // *TODO: Question: is this the right way out of this state
+ setState(stateSessionTerminated);
+ }
+ else if(!mMainSessionGroupHandle.empty())
+ {
+ setState(stateNoChannel);
+
+ // Start looped recording (needed for "panic button" anti-griefing tool)
+ recordingLoopStart();
+
+ // Initial kick-off of channel lookup logic
+ parcelChanged();
+ }
+ break;
+
+ //MARK: stateNoChannel
+ case stateNoChannel:
+
+ LL_DEBUGS("Voice") << "State No Channel" << LL_ENDL;
+ // 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
+ // joinedAudioSession() will transition from here to stateSessionJoined.
+ if(!mVoiceEnabled)
+ {
+ // User bailed out during connect -- jump straight to teardown.
+ setState(stateSessionTerminated);
+ }
+ else if(mSessionTerminateRequested)
+ {
+ 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(mAudioSession->mIsP2P)
+ {
+ sessionMediaDisconnectSendMessage(mAudioSession);
+ setState(stateSessionTerminated);
+ }
+ }
+ }
+ break;
+
+ //MARK: stateSessionJoined
+ case stateSessionJoined: // session handle received
+ // 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(mAudioSession && mAudioSession->mVoiceEnabled)
+ {
+ // Dirty state that may need to be sync'ed with the daemon.
+ mPTTDirty = true;
+ mSpeakerVolumeDirty = true;
+ mSpatialCoordsDirty = true;
+
+ setState(stateRunning);
+
+ // 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)
+ {
+ // User bailed out during connect -- jump straight to teardown.
+ setState(stateSessionTerminated);
+ }
+ else if(mSessionTerminateRequested)
+ {
+ // 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(mAudioSession && mAudioSession->mIsP2P)
+ {
+ sessionMediaDisconnectSendMessage(mAudioSession);
+ setState(stateSessionTerminated);
+ }
+ }
+ break;
+
+ //MARK: stateRunning
+ case stateRunning: // steady state
+ // Disabling voice or disconnect requested.
+ if(!mVoiceEnabled || mSessionTerminateRequested)
+ {
+ leaveAudioSession();
+ }
+ else
+ {
+
+ // Figure out whether the PTT state needs to change
+ {
+ bool newPTT;
+ if(mUsePTT)
+ {
+ // If configured to use PTT, track the user state.
+ newPTT = mUserPTTState;
+ }
+ else
+ {
+ // If not configured to use PTT, it should always be true (otherwise the user will be unable to speak).
+ newPTT = true;
+ }
+
+ if(mMuteMic)
+ {
+ // This always overrides any other PTT setting.
+ newPTT = false;
+ }
+
+ // Dirty if state changed.
+ if(newPTT != mPTT)
+ {
+ mPTT = newPTT;
+ mPTTDirty = true;
+ }
+ }
+
+ if(!inSpatialChannel())
+ {
+ // When in a non-spatial channel, never send positional updates.
+ mSpatialCoordsDirty = false;
+ }
+ else
+ {
+ // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position)
+ enforceTether();
+ }
+
+ // 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((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
+ {
+ mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
+ sendPositionalUpdate();
+ }
+ }
+ 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:
+
+ // 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;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+ }
+ else
+ {
+ 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);
+ }
+ else
+ {
+ // 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 AccountLoginStateChangeEvent will transition from here to stateLoggedOut.
+ break;
+
+ //MARK: stateLoggedOut
+ case stateLoggedOut: // logout response received
+
+ // Once we're logged out, all these things are invalid.
+ mAccountHandle.clear();
+ deleteAllSessions();
+ deleteAllBuddies();
+
+ if(mVoiceEnabled && !mRelogRequested)
+ {
+ // User was logged out, but wants to be logged in. Send a new login request.
+ setState(stateNeedsLogin);
+ }
+ else
+ {
+ // 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
+ setState(stateDisableCleanup);
+ break;
+
+ //MARK: stateConnectorFailed
+ case stateConnectorFailed:
+ setState(stateConnectorFailedWaiting);
+ break;
+ //MARK: stateConnectorFailedWaiting
+ case stateConnectorFailedWaiting:
+ if(!mVoiceEnabled)
+ {
+ setState(stateDisableCleanup);
+ }
+ break;
+
+ //MARK: stateLoginFailed
+ case stateLoginFailed:
+ setState(stateLoginFailedWaiting);
+ break;
+ //MARK: stateLoginFailedWaiting
+ case stateLoginFailedWaiting:
+ if(!mVoiceEnabled)
+ {
+ setState(stateDisableCleanup);
+ }
+ break;
+
+ //MARK: stateJoinSessionFailed
+ case stateJoinSessionFailed:
+ // Transition to error state. Send out any notifications here.
+ 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.
+ if(mSessionTerminateRequested)
+ {
+ setState(stateSessionTerminated);
+ }
+ break;
+
+ //MARK: stateJail
+ case stateJail:
+ // We have given up. Do nothing.
+ break;
+
+ }
+
+ if(mAudioSession && mAudioSession->mParticipantsChanged)
+ {
+ mAudioSession->mParticipantsChanged = false;
+ mAudioSessionChanged = true;
+ }
+
+ if(mAudioSessionChanged)
+ {
+ mAudioSessionChanged = false;
+ notifyParticipantObservers();
+ }
+}
+
+void LLVivoxVoiceClient::closeSocket(void)
+{
+ mSocket.reset();
+ mConnected = false;
+}
+
+void LLVivoxVoiceClient::loginSendMessage()
+{
+ std::ostringstream stream;
+
+ bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps");
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<AccountName>" << mAccountName << "</AccountName>"
+ << "<AccountPassword>" << mAccountPassword << "</AccountPassword>"
+ << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>"
+ << "<EnableBuddiesAndPresence>true</EnableBuddiesAndPresence>"
+ << "<BuddyManagementMode>Application</BuddyManagementMode>"
+ << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>"
+ << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"")
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::logout()
+{
+ // Ensure that we'll re-request provisioning before logging in again
+ mAccountPassword.clear();
+ mVoiceAccountServerURI.clear();
+
+ setState(stateLoggingOut);
+ logoutSendMessage();
+}
+
+void LLVivoxVoiceClient::logoutSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ mAccountHandle.clear();
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::accountListBlockRulesSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListBlockRules.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::accountListAutoAcceptRulesSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListAutoAcceptRules.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::sessionGroupCreateSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "creating session group" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<Type>Normal</Type>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText)
+{
+ LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
+
+ session->mCreateInProgress = true;
+ if(startAudio)
+ {
+ session->mMediaConnectInProgress = true;
+ }
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<URI>" << session->mSIPURI << "</URI>";
+
+ static const std::string allowed_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~";
+
+ if(!session->mHash.empty())
+ {
+ stream
+ << "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"
+ << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
+ }
+
+ stream
+ << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
+ << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+ << "<Name>" << mChannelName << "</Name>"
+ << "</Request>\n\n\n";
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)
+{
+ 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
+ << "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<URI>" << session->mSIPURI << "</URI>"
+ << "<Name>" << mChannelName << "</Name>"
+ << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
+ << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+ << "<Password>" << password << "</Password>"
+ << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"
+ << "</Request>\n\n\n"
+ ;
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
+{
+ LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL;
+
+ session->mMediaConnectInProgress = true;
+
+ std::ostringstream stream;
+
+ stream
+ << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<Media>Audio</Media>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionTextConnectSendMessage(sessionState *session)
+{
+ LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL;
+
+ std::ostringstream stream;
+
+ stream
+ << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionTerminate()
+{
+ mSessionTerminateRequested = true;
+}
+
+void LLVivoxVoiceClient::requestRelog()
+{
+ mSessionTerminateRequested = true;
+ mRelogRequested = true;
+}
+
+
+void LLVivoxVoiceClient::leaveAudioSession()
+{
+ if(mAudioSession)
+ {
+ 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;
+
+ 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 LLVivoxVoiceClient::sessionTerminateSendMessage(sessionState *session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(sessionState *session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<Media>Audio</Media>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+
+}
+
+void LLVivoxVoiceClient::sessionTextDisconnectSendMessage(sessionState *session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.TextDisconnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::getCaptureDevicesSendMessage()
+{
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::getRenderDevicesSendMessage()
+{
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::clearCaptureDevices()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ mCaptureDevices.clear();
+}
+
+void LLVivoxVoiceClient::addCaptureDevice(const std::string& name)
+{
+ LL_DEBUGS("Voice") << name << LL_ENDL;
+
+ mCaptureDevices.push_back(name);
+}
+
+LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices()
+{
+ return mCaptureDevices;
+}
+
+void LLVivoxVoiceClient::setCaptureDevice(const std::string& name)
+{
+ if(name == "Default")
+ {
+ if(!mCaptureDevice.empty())
+ {
+ mCaptureDevice.clear();
+ mCaptureDeviceDirty = true;
+ }
+ }
+ else
+ {
+ if(mCaptureDevice != name)
+ {
+ mCaptureDevice = name;
+ mCaptureDeviceDirty = true;
+ }
+ }
+}
+
+void LLVivoxVoiceClient::clearRenderDevices()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ mRenderDevices.clear();
+}
+
+void LLVivoxVoiceClient::addRenderDevice(const std::string& name)
+{
+ LL_DEBUGS("Voice") << name << LL_ENDL;
+ mRenderDevices.push_back(name);
+}
+
+LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices()
+{
+ return mRenderDevices;
+}
+
+void LLVivoxVoiceClient::setRenderDevice(const std::string& name)
+{
+ if(name == "Default")
+ {
+ if(!mRenderDevice.empty())
+ {
+ mRenderDevice.clear();
+ mRenderDeviceDirty = true;
+ }
+ }
+ else
+ {
+ if(mRenderDevice != name)
+ {
+ mRenderDevice = name;
+ mRenderDeviceDirty = true;
+ }
+ }
+
+}
+
+void LLVivoxVoiceClient::tuningStart()
+{
+ mTuningMode = true;
+ LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL;
+ if(getState() >= stateNoChannel)
+ {
+ LL_DEBUGS("Voice") << "no channel" << LL_ENDL;
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::tuningStop()
+{
+ mTuningMode = false;
+}
+
+bool LLVivoxVoiceClient::inTuningMode()
+{
+ bool result = false;
+ switch(getState())
+ {
+ case stateMicTuningRunning:
+ result = true;
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop)
+{
+ mTuningAudioFile = name;
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">"
+ << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
+ << "<Loop>" << (loop?"1":"0") << "</Loop>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::tuningRenderStopSendMessage()
+{
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
+ << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int duration)
+{
+ LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL;
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">"
+ << "<Duration>" << duration << "</Duration>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::tuningCaptureStopSendMessage()
+{
+ LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL;
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+
+ mTuningEnergy = 0.0f;
+}
+
+void LLVivoxVoiceClient::tuningSetMicVolume(float volume)
+{
+ int scaled_volume = scale_mic_volume(volume);
+
+ if(scaled_volume != mTuningMicVolume)
+ {
+ mTuningMicVolume = scaled_volume;
+ mTuningMicVolumeDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume)
+{
+ int scaled_volume = scale_speaker_volume(volume);
+
+ if(scaled_volume != mTuningSpeakerVolume)
+ {
+ mTuningSpeakerVolume = scaled_volume;
+ mTuningSpeakerVolumeDirty = true;
+ }
+}
+
+float LLVivoxVoiceClient::tuningGetEnergy(void)
+{
+ return mTuningEnergy;
+}
+
+bool LLVivoxVoiceClient::deviceSettingsAvailable()
+{
+ bool result = true;
+
+ if(!mConnected)
+ result = false;
+
+ if(mRenderDevices.empty())
+ result = false;
+
+ return result;
+}
+
+void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList)
+{
+ if(clearCurrentList)
+ {
+ clearCaptureDevices();
+ clearRenderDevices();
+ }
+ getCaptureDevicesSendMessage();
+ getRenderDevicesSendMessage();
+}
+
+void LLVivoxVoiceClient::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;
+
+ // Try to relaunch the daemon
+ setState(stateDisableCleanup);
+}
+
+void LLVivoxVoiceClient::giveUp()
+{
+ // All has failed. Clean up and stop trying.
+ closeSocket();
+ deleteAllSessions();
+ deleteAllBuddies();
+
+ setState(stateJail);
+}
+
+static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel)
+{
+ F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the new position and velocity
+ F64 npos[3];
+
+ // The original XML command was sent like this:
+ /*
+ << "<Position>"
+ << "<X>" << pos[VX] << "</X>"
+ << "<Y>" << pos[VZ] << "</Y>"
+ << "<Z>" << pos[VY] << "</Z>"
+ << "</Position>"
+ << "<Velocity>"
+ << "<X>" << mAvatarVelocity[VX] << "</X>"
+ << "<Y>" << mAvatarVelocity[VZ] << "</Y>"
+ << "<Z>" << mAvatarVelocity[VY] << "</Z>"
+ << "</Velocity>"
+ << "<AtOrientation>"
+ << "<X>" << l.mV[VX] << "</X>"
+ << "<Y>" << u.mV[VX] << "</Y>"
+ << "<Z>" << a.mV[VX] << "</Z>"
+ << "</AtOrientation>"
+ << "<UpOrientation>"
+ << "<X>" << l.mV[VZ] << "</X>"
+ << "<Y>" << u.mV[VY] << "</Y>"
+ << "<Z>" << a.mV[VZ] << "</Z>"
+ << "</UpOrientation>"
+ << "<LeftOrientation>"
+ << "<X>" << l.mV [VY] << "</X>"
+ << "<Y>" << u.mV [VZ] << "</Y>"
+ << "<Z>" << a.mV [VY] << "</Z>"
+ << "</LeftOrientation>";
+ */
+
+#if 1
+ // This was the original transform done when building the XML command
+ nat[0] = left.mV[VX];
+ nat[1] = up.mV[VX];
+ nat[2] = at.mV[VX];
+
+ nup[0] = left.mV[VZ];
+ nup[1] = up.mV[VY];
+ nup[2] = at.mV[VZ];
+
+ nl[0] = left.mV[VY];
+ nl[1] = up.mV[VZ];
+ nl[2] = at.mV[VY];
+
+ npos[0] = pos.mdV[VX];
+ npos[1] = pos.mdV[VZ];
+ npos[2] = pos.mdV[VY];
+
+ nvel[0] = vel.mV[VX];
+ nvel[1] = vel.mV[VZ];
+ nvel[2] = vel.mV[VY];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+
+ // This was the original transform done in the SDK
+ nat[0] = at.mV[2];
+ nat[1] = 0; // y component of at vector is always 0, this was up[2]
+ nat[2] = -1 * left.mV[2];
+
+ // We override whatever the application gives us
+ nup[0] = 0; // x component of up vector is always 0
+ nup[1] = 1; // y component of up vector is always 1
+ nup[2] = 0; // z component of up vector is always 0
+
+ nl[0] = at.mV[0];
+ nl[1] = 0; // y component of left vector is always zero, this was up[0]
+ nl[2] = -1 * left.mV[0];
+
+ npos[2] = pos.mdV[2] * -1.0;
+ npos[1] = pos.mdV[1];
+ npos[0] = pos.mdV[0];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+#else
+ // This is the compose of the two transforms (at least, that's what I'm trying for)
+ nat[0] = at.mV[VX];
+ nat[1] = 0; // y component of at vector is always 0, this was up[2]
+ nat[2] = -1 * up.mV[VZ];
+
+ // We override whatever the application gives us
+ nup[0] = 0; // x component of up vector is always 0
+ nup[1] = 1; // y component of up vector is always 1
+ nup[2] = 0; // z component of up vector is always 0
+
+ nl[0] = left.mV[VX];
+ nl[1] = 0; // y component of left vector is always zero, this was up[0]
+ nl[2] = -1 * left.mV[VY];
+
+ npos[0] = pos.mdV[VX];
+ npos[1] = pos.mdV[VZ];
+ npos[2] = pos.mdV[VY] * -1.0;
+
+ nvel[0] = vel.mV[VX];
+ nvel[1] = vel.mV[VZ];
+ nvel[2] = vel.mV[VY];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+
+#endif
+}
+
+void LLVivoxVoiceClient::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 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">"
+ << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>";
+
+ stream << "<SpeakerPosition>";
+
+// 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
+ << "<Position>"
+ << "<X>" << pos.mdV[VX] << "</X>"
+ << "<Y>" << pos.mdV[VY] << "</Y>"
+ << "<Z>" << pos.mdV[VZ] << "</Z>"
+ << "</Position>"
+ << "<Velocity>"
+ << "<X>" << vel.mV[VX] << "</X>"
+ << "<Y>" << vel.mV[VY] << "</Y>"
+ << "<Z>" << vel.mV[VZ] << "</Z>"
+ << "</Velocity>"
+ << "<AtOrientation>"
+ << "<X>" << a.mV[VX] << "</X>"
+ << "<Y>" << a.mV[VY] << "</Y>"
+ << "<Z>" << a.mV[VZ] << "</Z>"
+ << "</AtOrientation>"
+ << "<UpOrientation>"
+ << "<X>" << u.mV[VX] << "</X>"
+ << "<Y>" << u.mV[VY] << "</Y>"
+ << "<Z>" << u.mV[VZ] << "</Z>"
+ << "</UpOrientation>"
+ << "<LeftOrientation>"
+ << "<X>" << l.mV [VX] << "</X>"
+ << "<Y>" << l.mV [VY] << "</Y>"
+ << "<Z>" << l.mV [VZ] << "</Z>"
+ << "</LeftOrientation>";
+
+ stream << "</SpeakerPosition>";
+
+ stream << "<ListenerPosition>";
+
+ LLVector3d earPosition;
+ LLVector3 earVelocity;
+ LLMatrix3 earRot;
+
+ switch(mEarLocation)
+ {
+ case earLocCamera:
+ default:
+ earPosition = mCameraPosition;
+ earVelocity = mCameraVelocity;
+ earRot = mCameraRot;
+ break;
+
+ case earLocAvatar:
+ earPosition = mAvatarPosition;
+ earVelocity = mAvatarVelocity;
+ earRot = mAvatarRot;
+ break;
+
+ case earLocMixed:
+ earPosition = mAvatarPosition;
+ earVelocity = mAvatarVelocity;
+ earRot = mCameraRot;
+ break;
+ }
+
+ l = earRot.getLeftRow();
+ u = earRot.getUpRow();
+ a = earRot.getFwdRow();
+ pos = earPosition;
+ vel = earVelocity;
+
+// LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;
+
+ oldSDKTransform(l, u, a, pos, vel);
+
+ stream
+ << "<Position>"
+ << "<X>" << pos.mdV[VX] << "</X>"
+ << "<Y>" << pos.mdV[VY] << "</Y>"
+ << "<Z>" << pos.mdV[VZ] << "</Z>"
+ << "</Position>"
+ << "<Velocity>"
+ << "<X>" << vel.mV[VX] << "</X>"
+ << "<Y>" << vel.mV[VY] << "</Y>"
+ << "<Z>" << vel.mV[VZ] << "</Z>"
+ << "</Velocity>"
+ << "<AtOrientation>"
+ << "<X>" << a.mV[VX] << "</X>"
+ << "<Y>" << a.mV[VY] << "</Y>"
+ << "<Z>" << a.mV[VZ] << "</Z>"
+ << "</AtOrientation>"
+ << "<UpOrientation>"
+ << "<X>" << u.mV[VX] << "</X>"
+ << "<Y>" << u.mV[VY] << "</Y>"
+ << "<Z>" << u.mV[VZ] << "</Z>"
+ << "</UpOrientation>"
+ << "<LeftOrientation>"
+ << "<X>" << l.mV [VX] << "</X>"
+ << "<Y>" << l.mV [VY] << "</Y>"
+ << "<Z>" << l.mV [VZ] << "</Z>"
+ << "</LeftOrientation>";
+
+
+ stream << "</ListenerPosition>";
+
+ stream << "</Request>\n\n\n";
+ }
+
+ if(mAudioSession && mAudioSession->mVolumeDirty)
+ {
+ participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
+
+ mAudioSession->mVolumeDirty = false;
+
+ for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
+ {
+ participantState *p = iter->second;
+
+ if(p->mVolumeDirty)
+ {
+ // Can't set volume/mute for yourself
+ if(!p->mIsSelf)
+ {
+ int volume = 56; // nominal default value
+ bool mute = p->mOnMuteList;
+
+ if(p->mUserVolume != -1)
+ {
+ // scale from user volume in the range 0-400 (with 100 as "normal") to vivox volume in the range 0-100 (with 56 as "normal")
+ if(p->mUserVolume < 100)
+ volume = (p->mUserVolume * 56) / 100;
+ else
+ volume = (((p->mUserVolume - 100) * (100 - 56)) / 300) + 56;
+ }
+ else if(p->mVolume != -1)
+ {
+ // Use the previously reported internal volume (comes in with a ParticipantUpdatedEvent)
+ volume = p->mVolume;
+ }
+
+
+ if(mute)
+ {
+ // SetParticipantMuteForMe doesn't work in p2p sessions.
+ // If we want the user to be muted, set their volume to 0 as well.
+ // This isn't perfect, but it will at least reduce their volume to a minimum.
+ volume = 0;
+ }
+
+ if(volume == 0)
+ mute = true;
+
+ LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL;
+
+ // SLIM SDK: Send both volume and mute commands.
+
+ // Send a "volume for me" command for the user.
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">"
+ << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
+ << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
+ << "<Volume>" << volume << "</Volume>"
+ << "</Request>\n\n\n";
+
+ // Send a "mute for me" command for the user
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">"
+ << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
+ << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
+ << "<Mute>" << (mute?"1":"0") << "</Mute>"
+ << "</Request>\n\n\n";
+ }
+
+ p->mVolumeDirty = false;
+ }
+ }
+ }
+
+ buildLocalAudioUpdates(stream);
+
+ if(!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+
+ // Friends list updates can be huge, especially on the first voice login of an account with lots of friends.
+ // Batching them all together can choke SLVoice, so send them in separate writes.
+ sendFriendsListUpdates();
+}
+
+void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
+{
+ if(mCaptureDeviceDirty)
+ {
+ LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">"
+ << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>"
+ << "</Request>"
+ << "\n\n\n";
+
+ mCaptureDeviceDirty = false;
+ }
+}
+
+void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
+{
+ if(mRenderDeviceDirty)
+ {
+ LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
+ << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
+ << "</Request>"
+ << "\n\n\n";
+ mRenderDeviceDirty = false;
+ }
+}
+
+void LLVivoxVoiceClient::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)
+
+ LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<Value>" << (mPTT?"false":"true") << "</Value>"
+ << "</Request>\n\n\n";
+
+ }
+
+ if(mSpeakerMuteDirty)
+ {
+ const char *muteval = ((mSpeakerVolume == 0)?"true":"false");
+
+ mSpeakerMuteDirty = false;
+
+ LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<Value>" << muteval << "</Value>"
+ << "</Request>\n\n\n";
+
+ }
+
+ if(mSpeakerVolumeDirty)
+ {
+ mSpeakerVolumeDirty = false;
+
+ LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<Value>" << mSpeakerVolume << "</Value>"
+ << "</Request>\n\n\n";
+
+ }
+
+ if(mMicVolumeDirty)
+ {
+ mMicVolumeDirty = false;
+
+ LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<Value>" << mMicVolume << "</Value>"
+ << "</Request>\n\n\n";
+ }
+
+
+}
+
+void LLVivoxVoiceClient::checkFriend(const LLUUID& id)
+{
+ std::string name;
+ buddyListEntry *buddy = findBuddy(id);
+
+ // Make sure we don't add a name before it's been looked up.
+ if(gCacheName->getFullName(id, name))
+ {
+
+ const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id);
+ bool canSeeMeOnline = false;
+ if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS))
+ canSeeMeOnline = true;
+
+ // When we get here, mNeedsSend is true and mInSLFriends is false. Change them as necessary.
+
+ if(buddy)
+ {
+ // This buddy is already in both lists.
+
+ if(name != buddy->mDisplayName)
+ {
+ // The buddy is in the list with the wrong name. Update it with the correct name.
+ LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL;
+ buddy->mDisplayName = name;
+ buddy->mNeedsNameUpdate = true; // This will cause the buddy to be resent.
+ }
+ }
+ else
+ {
+ // This buddy was not in the vivox list, needs to be added.
+ buddy = addBuddy(sipURIFromID(id), name);
+ buddy->mUUID = id;
+ }
+
+ // In all the above cases, the buddy is in the SL friends list (which is how we got here).
+ buddy->mInSLFriends = true;
+ buddy->mCanSeeMeOnline = canSeeMeOnline;
+ buddy->mNameResolved = true;
+
+ }
+ else
+ {
+ // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has.
+ if(buddy)
+ {
+ buddy->mNameResolved = false;
+ }
+
+ // Initiate a lookup.
+ // The "lookup completed" callback will ensure that the friends list is rechecked after it completes.
+ lookupName(id);
+ }
+}
+
+void LLVivoxVoiceClient::clearAllLists()
+{
+ // FOR TESTING ONLY
+
+ // This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about.
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
+ {
+ buddyListEntry *buddy = buddy_it->second;
+ buddy_it++;
+
+ std::ostringstream stream;
+
+ if(buddy->mInVivoxBuddies)
+ {
+ // delete this entry from the vivox buddy list
+ buddy->mInVivoxBuddies = false;
+ LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+ << "</Request>\n\n\n";
+ }
+
+ if(buddy->mHasBlockListEntry)
+ {
+ // Delete the associated block list entry (so the block list doesn't fill up with junk)
+ buddy->mHasBlockListEntry = false;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BlockMask>" << buddy->mURI << "</BlockMask>"
+ << "</Request>\n\n\n";
+ }
+ if(buddy->mHasAutoAcceptListEntry)
+ {
+ // Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk)
+ buddy->mHasAutoAcceptListEntry = false;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+ << "</Request>\n\n\n";
+ }
+
+ writeString(stream.str());
+
+ }
+}
+
+void LLVivoxVoiceClient::sendFriendsListUpdates()
+{
+ if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty)
+ {
+ mFriendsListDirty = false;
+
+ if(0)
+ {
+ // FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries.
+ clearAllLists();
+ return;
+ }
+
+ LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL;
+
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
+ {
+ // reset the temp flags in the local buddy list
+ buddy_it->second->mInSLFriends = false;
+ }
+
+ // correlate with the friends list
+ {
+ LLCollectAllBuddies collect;
+ LLAvatarTracker::instance().applyFunctor(collect);
+ LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin();
+ LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end();
+
+ for ( ; it != end; ++it)
+ {
+ checkFriend(it->second);
+ }
+ it = collect.mOffline.begin();
+ end = collect.mOffline.end();
+ for ( ; it != end; ++it)
+ {
+ checkFriend(it->second);
+ }
+ }
+
+ LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL;
+
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
+ {
+ buddyListEntry *buddy = buddy_it->second;
+ buddy_it++;
+
+ // Ignore entries that aren't resolved yet.
+ if(buddy->mNameResolved)
+ {
+ std::ostringstream stream;
+
+ if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate))
+ {
+ if(mNumberOfAliases > 0)
+ {
+ // Add (or update) this entry in the vivox buddy list
+ buddy->mInVivoxBuddies = true;
+ buddy->mNeedsNameUpdate = false;
+ LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddySet.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+ << "<DisplayName>" << buddy->mDisplayName << "</DisplayName>"
+ << "<BuddyData></BuddyData>" // Without this, SLVoice doesn't seem to parse the command.
+ << "<GroupID>0</GroupID>"
+ << "</Request>\n\n\n";
+ }
+ }
+ else if(!buddy->mInSLFriends)
+ {
+ // This entry no longer exists in your SL friends list. Remove all traces of it from the Vivox buddy list.
+ if(buddy->mInVivoxBuddies)
+ {
+ // delete this entry from the vivox buddy list
+ buddy->mInVivoxBuddies = false;
+ LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+ << "</Request>\n\n\n";
+ }
+
+ if(buddy->mHasBlockListEntry)
+ {
+ // Delete the associated block list entry, if any
+ buddy->mHasBlockListEntry = false;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BlockMask>" << buddy->mURI << "</BlockMask>"
+ << "</Request>\n\n\n";
+ }
+ if(buddy->mHasAutoAcceptListEntry)
+ {
+ // Delete the associated auto-accept list entry, if any
+ buddy->mHasAutoAcceptListEntry = false;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+ << "</Request>\n\n\n";
+ }
+ }
+
+ if(buddy->mInSLFriends)
+ {
+
+ if(buddy->mCanSeeMeOnline)
+ {
+ // Buddy should not be blocked.
+
+ // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
+
+ // If the buddy has a block list entry, delete it.
+ if(buddy->mHasBlockListEntry)
+ {
+ buddy->mHasBlockListEntry = false;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BlockMask>" << buddy->mURI << "</BlockMask>"
+ << "</Request>\n\n\n";
+
+
+ // If we just deleted a block list entry, add an auto-accept entry.
+ if(!buddy->mHasAutoAcceptListEntry)
+ {
+ buddy->mHasAutoAcceptListEntry = true;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateAutoAcceptRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+ << "<AutoAddAsBuddy>0</AutoAddAsBuddy>"
+ << "</Request>\n\n\n";
+ }
+ }
+ }
+ else
+ {
+ // Buddy should be blocked.
+
+ // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
+
+ // If this buddy has an autoaccept entry, delete it
+ if(buddy->mHasAutoAcceptListEntry)
+ {
+ buddy->mHasAutoAcceptListEntry = false;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+ << "</Request>\n\n\n";
+
+ // If we just deleted an auto-accept entry, add a block list entry.
+ if(!buddy->mHasBlockListEntry)
+ {
+ buddy->mHasBlockListEntry = true;
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateBlockRule.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BlockMask>" << buddy->mURI << "</BlockMask>"
+ << "<PresenceOnly>1</PresenceOnly>"
+ << "</Request>\n\n\n";
+ }
+ }
+ }
+
+ if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies)
+ {
+ // Delete this entry from the local buddy list. This should NOT invalidate the iterator,
+ // since it has already been incremented to the next entry.
+ deleteBuddy(buddy->mURI);
+ }
+
+ }
+ writeString(stream.str());
+ }
+ }
+ }
+}
+
+/////////////////////////////
+// Response/Event handlers
+
+void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID)
+{
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL;
+ setState(stateConnectorFailed);
+ LLSD args;
+ std::stringstream errs;
+ errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000";
+ args["HOSTID"] = errs.str();
+ if (LLGridManager::getInstance()->isSystemGrid())
+ {
+ LLNotificationsUtil::add("NoVoiceConnect", args);
+ }
+ else
+ {
+ LLNotificationsUtil::add("NoVoiceConnect-GIAB", args);
+ }
+ }
+ else
+ {
+ // Connector created, move forward.
+ LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL;
+ mVoiceVersion.serverVersion = versionID;
+ mConnectorHandle = connectorHandle;
+ if(getState() == stateConnectorStarting)
+ {
+ setState(stateConnectorStarted);
+ }
+ }
+}
+
+void LLVivoxVoiceClient::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)
+// {
+// setState(stateLoggedIn);
+// }
+ }
+}
+
+void LLVivoxVoiceClient::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)
+ {
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ if(session == mAudioSession)
+ {
+ setState(stateJoinSessionFailed);
+ }
+ else
+ {
+ reapSession(session);
+ }
+ }
+ }
+ else
+ {
+ LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
+ if(session)
+ {
+ setSessionHandle(session, sessionHandle);
+ }
+ }
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString)
+{
+ sessionState *session = findSession(requestId);
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ if(session)
+ {
+ 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 LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString)
+{
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL;
+ // Should this ever fail? do we care if it does?
+ }
+}
+
+void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
+{
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL;
+ // Should this ever fail? do we care if it does?
+ }
+
+ mConnected = false;
+
+ if(getState() == stateConnectorStopping)
+ {
+ setState(stateConnectorStopped);
+ }
+}
+
+void LLVivoxVoiceClient::sessionAddedEvent(
+ std::string &uriString,
+ std::string &alias,
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ bool isChannel,
+ bool incoming,
+ std::string &nameString,
+ std::string &applicationString)
+{
+ sessionState *session = NULL;
+
+ 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))
+ {
+ // Normal URI(base64-encoded UUID)
+ }
+ else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID))
+ {
+ // 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_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL;
+ setUUIDFromStringHash(session->mCallerID, session->mSIPURI);
+ session->mSynthesizedCallerID = true;
+
+ // Can't look up the name in this case -- we have to extract it from the URI.
+ std::string namePortion = nameFromsipURI(session->mSIPURI);
+ if(namePortion.empty())
+ {
+ // Didn't seem to be a SIP URI, just use the whole provided name.
+ namePortion = nameString;
+ }
+
+ // Some incoming names may be separated with an underscore instead of a space. Fix this.
+ LLStringUtil::replaceChar(namePortion, '_', ' ');
+
+ // Act like we just finished resolving the name (this stores it in all the right places)
+ avatarNameResolved(session->mCallerID, namePortion);
+ }
+
+ LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL;
+
+ if(!session->mSynthesizedCallerID)
+ {
+ // If we got here, we don't have a proper name. Initiate a lookup.
+ lookupName(session->mCallerID);
+ }
+ }
+ }
+}
+
+void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle)
+{
+ LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL;
+
+#if USE_SESSION_GROUPS
+ if(mMainSessionGroupHandle.empty())
+ {
+ // This is the first (i.e. "main") session group. Save its handle.
+ mMainSessionGroupHandle = sessionGroupHandle;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL;
+ }
+#endif
+}
+
+void LLVivoxVoiceClient::joinedAudioSession(sessionState *session)
+{
+ LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL;
+ if(mAudioSession != session)
+ {
+ sessionState *oldSession = mAudioSession;
+
+ mAudioSession = session;
+ mAudioSessionChanged = true;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+ }
+
+ // This is the session we're joining.
+ if(getState() == stateJoiningSession)
+ {
+ setState(stateSessionJoined);
+
+ // SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now.
+ // Add the current user as a participant here.
+ participantState *participant = session->addParticipant(sipURIFromName(mAccountName));
+ if(participant)
+ {
+ participant->mIsSelf = true;
+ lookupName(participant->mAvatarID);
+
+ 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)
+ {
+ lookupName(participant->mAvatarID);
+ }
+ else if(!session->mName.empty())
+ {
+ 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;
+ }
+ }
+ }
+}
+
+void LLVivoxVoiceClient::sessionRemovedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle)
+{
+ LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL;
+
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ leftAudioSession(session);
+
+ // This message invalidates the session's handle. Set it to empty.
+ setSessionHandle(session);
+
+ // This also means that the session's session group is now empty.
+ // Terminate the session group so it doesn't leak.
+ sessionGroupTerminateSendMessage(session);
+
+ // Reset the media state (we now have no info)
+ session->mMediaStreamState = streamStateUnknown;
+ session->mTextStreamState = streamStateUnknown;
+
+ // Conditionally delete the session
+ reapSession(session);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::reapSession(sessionState *session)
+{
+ if(session)
+ {
+ if(!session->mHandle.empty())
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL;
+ }
+ else if(session->mCreateInProgress)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL;
+ }
+ else if(session->mMediaConnectInProgress)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL;
+ }
+ else if(session == mAudioSession)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL;
+ }
+ else if(session == mNextAudioSession)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL;
+ }
+ else
+ {
+ // TODO: Question: Should we check for queued text messages here?
+ // We don't have a reason to keep tracking this session, so just delete it.
+ LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL;
+ deleteSession(session);
+ session = NULL;
+ }
+ }
+ else
+ {
+// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL;
+ }
+}
+
+// Returns true if the session seems to indicate we've moved to a region on a different voice server
+bool LLVivoxVoiceClient::sessionNeedsRelog(sessionState *session)
+{
+ bool result = false;
+
+ if(session != NULL)
+ {
+ // Only make this check for spatial channels (so it won't happen for group or p2p calls)
+ if(session->mIsSpatial)
+ {
+ std::string::size_type atsign;
+
+ atsign = session->mSIPURI.find("@");
+
+ if(atsign != std::string::npos)
+ {
+ std::string urihost = session->mSIPURI.substr(atsign + 1);
+ if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str()))
+ {
+ // 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;
+}
+
+void LLVivoxVoiceClient::leftAudioSession(
+ sessionState *session)
+{
+ if(mAudioSession == session)
+ {
+ switch(getState())
+ {
+ case stateJoiningSession:
+ case stateSessionJoined:
+ case stateRunning:
+ case stateLeavingSession:
+ case stateJoinSessionFailed:
+ case stateJoinSessionFailedWaiting:
+ // normal transition
+ LL_DEBUGS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL;
+ setState(stateSessionTerminated);
+ break;
+
+ case stateSessionTerminated:
+ // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state.
+ LL_WARNS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL;
+ break;
+
+ default:
+ LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL;
+ setState(stateSessionTerminated);
+ break;
+ }
+ }
+}
+
+void LLVivoxVoiceClient::accountLoginStateChangeEvent(
+ std::string &accountHandle,
+ int statusCode,
+ std::string &statusString,
+ int state)
+{
+ /*
+ 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") << "state change event: " << state << LL_ENDL;
+ switch(state)
+ {
+ case 1:
+ if(getState() == stateLoggingIn)
+ {
+ setState(stateLoggedIn);
+ }
+ break;
+
+ case 3:
+ // The user is in the process of logging out.
+ setState(stateLoggingOut);
+ break;
+
+ case 0:
+ // The user has been logged out.
+ setState(stateLoggedOut);
+ break;
+
+ default:
+ //Used to be a commented out warning
+ LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL;
+ break;
+ }
+}
+
+void LLVivoxVoiceClient::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)
+ {
+ // We know about this session
+
+ // Save the state for later use
+ session->mMediaStreamState = state;
+
+ switch(statusCode)
+ {
+ case 0:
+ case 200:
+ // generic success
+ // Don't change the saved error code (it may have been set elsewhere)
+ break;
+ default:
+ // save the status code for later
+ session->mErrorStatusCode = statusCode;
+ break;
+ }
+
+ switch(state)
+ {
+ case streamStateIdle:
+ // Standard "left audio session"
+ session->mVoiceEnabled = false;
+ session->mMediaConnectInProgress = false;
+ leftAudioSession(session);
+ break;
+
+ case streamStateConnected:
+ session->mVoiceEnabled = true;
+ session->mMediaConnectInProgress = false;
+ joinedAudioSession(session);
+ break;
+
+ case streamStateRinging:
+ if(incoming)
+ {
+ // Send the voice chat invite to the GUI layer
+ // TODO: Question: Should we correlate with the mute list here?
+ session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID);
+ session->mVoiceInvitePending = true;
+ if(session->mName.empty())
+ {
+ lookupName(session->mCallerID);
+ }
+ else
+ {
+ // Act like we just finished resolving the name
+ avatarNameResolved(session->mCallerID, session->mName);
+ }
+ }
+ break;
+
+ default:
+ LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
+ break;
+
+ }
+
+ }
+ else
+ {
+ LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::textStreamUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ bool enabled,
+ int state,
+ bool incoming)
+{
+ sessionState *session = findSession(sessionHandle);
+
+ if(session)
+ {
+ // Save the state for later use
+ session->mTextStreamState = state;
+
+ // We know about this session
+ switch(state)
+ {
+ case 0: // We see this when the text stream closes
+ LL_DEBUGS("Voice") << "stream closed" << LL_ENDL;
+ break;
+
+ case 1: // We see this on an incoming call from the Connector
+ // Try to send any text messages queued for this session.
+ sendQueuedTextMessages(session);
+
+ // Send the text chat invite to the GUI layer
+ // TODO: Question: Should we correlate with the mute list here?
+ session->mTextInvitePending = true;
+ if(session->mName.empty())
+ {
+ 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 LLVivoxVoiceClient::participantAddedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &nameString,
+ std::string &displayNameString,
+ int participantType)
+{
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ participantState *participant = session->addParticipant(uriString);
+ if(participant)
+ {
+ participant->mAccountName = nameString;
+
+ LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+
+ 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 LLVivoxVoiceClient::participantRemovedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &nameString)
+{
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ participantState *participant = session->findParticipant(uriString);
+ if(participant)
+ {
+ session->removeParticipant(participant);
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
+ }
+}
+
+
+void LLVivoxVoiceClient::participantUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ bool isModeratorMuted,
+ bool isSpeaking,
+ int volume,
+ F32 energy)
+{
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ participantState *participant = session->findParticipant(uriString);
+
+ if(participant)
+ {
+ 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;
+
+ // *HACK: mantipov: added while working on EXT-3544
+ /*
+ Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE
+ LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER.
+
+ participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted
+ Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug.
+ Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates.
+
+ But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post()
+ voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager
+ and event is not fired.
+
+ So, we have to call LLSpeakerMgr::update() here. In any case it is better than call it
+ in LLCallFloater::draw()
+ */
+ LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel();
+
+ // ignore session ID of local chat
+ if (voice_cnl && voice_cnl->getSessionID().notNull())
+ {
+ LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID());
+ if (speaker_manager)
+ {
+ speaker_manager->update(true);
+ }
+ }
+
+ }
+ else
+ {
+ LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::buddyPresenceEvent(
+ std::string &uriString,
+ std::string &alias,
+ std::string &statusString,
+ std::string &applicationString)
+{
+ buddyListEntry *buddy = findBuddy(uriString);
+
+ if(buddy)
+ {
+ LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL;
+ LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
+
+ if(applicationString.empty())
+ {
+ // This presence event is from a client that doesn't set up the Application string. Do things the old-skool way.
+ // NOTE: this will be needed to support people who aren't on the 3010-class SDK yet.
+
+ if ( stricmp("Unknown", statusString.c_str())== 0)
+ {
+ // User went offline with a non-SLim-enabled viewer.
+ buddy->mOnlineSL = false;
+ }
+ else if ( stricmp("Online", statusString.c_str())== 0)
+ {
+ // User came online with a non-SLim-enabled viewer.
+ buddy->mOnlineSL = true;
+ }
+ else
+ {
+ // If the user is online through SLim, their status will be "Online-slc", "Away", or something else.
+ // NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string.
+ buddy->mOnlineSLim = true;
+ }
+ }
+ else if(applicationString.find("SecondLifeViewer") != std::string::npos)
+ {
+ // This presence event is from a viewer that sets the application string
+ if ( stricmp("Unknown", statusString.c_str())== 0)
+ {
+ // Viewer says they're offline
+ buddy->mOnlineSL = false;
+ }
+ else
+ {
+ // Viewer says they're online
+ buddy->mOnlineSL = true;
+ }
+ }
+ else
+ {
+ // This presence event is from something which is NOT the SL viewer (assume it's SLim).
+ if ( stricmp("Unknown", statusString.c_str())== 0)
+ {
+ // SLim says they're offline
+ buddy->mOnlineSLim = false;
+ }
+ else
+ {
+ // SLim says they're online
+ buddy->mOnlineSLim = true;
+ }
+ }
+
+ LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
+
+ // HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change.
+ LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID));
+
+ notifyFriendObservers();
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::messageEvent(
+ std::string &sessionHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &messageHeader,
+ std::string &messageBody,
+ std::string &applicationString)
+{
+ LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL;
+// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL;
+
+ if(messageHeader.find("text/html") != std::string::npos)
+ {
+ std::string message;
+
+ {
+ const std::string startMarker = "<body";
+ const std::string startMarker2 = ">";
+ const std::string endMarker = "</body>";
+ const std::string startSpan = "<span";
+ const std::string endSpan = "</span>";
+ std::string::size_type start;
+ std::string::size_type end;
+
+ // Default to displaying the raw string, so the message gets through.
+ message = messageBody;
+
+ // Find the actual message text within the XML fragment
+ start = messageBody.find(startMarker);
+ start = messageBody.find(startMarker2, start);
+ end = messageBody.find(endMarker);
+
+ if(start != std::string::npos)
+ {
+ start += startMarker2.size();
+
+ if(end != std::string::npos)
+ end -= start;
+
+ message.assign(messageBody, start, end);
+ }
+ else
+ {
+ // Didn't find a <body>, try looking for a <span> instead.
+ start = messageBody.find(startSpan);
+ start = messageBody.find(startMarker2, start);
+ end = messageBody.find(endSpan);
+
+ if(start != std::string::npos)
+ {
+ start += startMarker2.size();
+
+ if(end != std::string::npos)
+ end -= start;
+
+ message.assign(messageBody, start, end);
+ }
+ }
+ }
+
+// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL;
+
+ // strip formatting tags
+ {
+ std::string::size_type start;
+ std::string::size_type end;
+
+ while((start = message.find('<')) != std::string::npos)
+ {
+ if((end = message.find('>', start + 1)) != std::string::npos)
+ {
+ // Strip out the tag
+ message.erase(start, (end + 1) - start);
+ }
+ else
+ {
+ // Avoid an infinite loop
+ break;
+ }
+ }
+ }
+
+ // Decode ampersand-escaped chars
+ {
+ std::string::size_type mark = 0;
+
+ // The text may contain text encoded with &lt;, &gt;, and &amp;
+ mark = 0;
+ while((mark = message.find("&lt;", mark)) != std::string::npos)
+ {
+ message.replace(mark, 4, "<");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = message.find("&gt;", mark)) != std::string::npos)
+ {
+ message.replace(mark, 4, ">");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = message.find("&amp;", mark)) != std::string::npos)
+ {
+ message.replace(mark, 5, "&");
+ mark += 1;
+ }
+ }
+
+ // strip leading/trailing whitespace (since we always seem to get a couple newlines)
+ LLStringUtil::trim(message);
+
+// LL_DEBUGS("Voice") << " stripped message = \n" << message << 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?
+ }
+
+ 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(),
+ message.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(": ") + message;
+ // 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 LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string &notificationType)
+{
+ sessionState *session = findSession(sessionHandle);
+
+ if(session)
+ {
+ participantState *participant = session->findParticipant(uriString);
+ if(participant)
+ {
+ if (!stricmp(notificationType.c_str(), "Typing"))
+ {
+ // Other end started typing
+ // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart().
+ // It requires an LLIMInfo for the message, which we don't have here.
+ }
+ else if (!stricmp(notificationType.c_str(), "NotTyping"))
+ {
+ // Other end stopped typing
+ // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop().
+ // It requires an LLIMInfo for the message, which we don't have here.
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType)
+{
+ buddyListEntry *buddy = findBuddy(buddyURI);
+
+ if(!buddy)
+ {
+ // Couldn't find buddy by URI, try converting the alias...
+ if(!alias.empty())
+ {
+ LLUUID id;
+ if(IDFromName(alias, id))
+ {
+ buddy = findBuddy(id);
+ }
+ }
+ }
+
+ if(buddy)
+ {
+ std::ostringstream stream;
+
+ if(buddy->mCanSeeMeOnline)
+ {
+ // Sending the response will create an auto-accept rule
+ buddy->mHasAutoAcceptListEntry = true;
+ }
+ else
+ {
+ // Sending the response will create a block rule
+ buddy->mHasBlockListEntry = true;
+ }
+
+ if(buddy->mInSLFriends)
+ {
+ buddy->mInVivoxBuddies = true;
+ }
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.SendSubscriptionReply.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+ << "<RuleType>" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "</RuleType>"
+ << "<AutoAccept>"<< (buddy->mInSLFriends?"1":"0")<< "</AutoAccept>"
+ << "<SubscriptionHandle>" << subscriptionHandle << "</SubscriptionHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy)
+{
+ LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
+ mTuningEnergy = energy;
+}
+
+void LLVivoxVoiceClient::buddyListChanged()
+{
+ // This is called after we receive a BuddyAndGroupListChangedEvent.
+ mBuddyListMapPopulated = true;
+ mFriendsListDirty = true;
+}
+
+void LLVivoxVoiceClient::muteListChanged()
+{
+ // 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 LLVivoxVoiceClient::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
+LLVivoxVoiceClient::participantState::participantState(const std::string &uri) :
+ mURI(uri),
+ mPTT(false),
+ mIsSpeaking(false),
+ mIsModeratorMuted(false),
+ mLastSpokeTimestamp(0.f),
+ mPower(0.f),
+ mVolume(-1),
+ mOnMuteList(false),
+ mUserVolume(-1),
+ mVolumeDirty(false),
+ mAvatarIDValid(false),
+ mIsSelf(false)
+{
+}
+
+LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri)
+{
+ participantState *result = NULL;
+ bool useAlternateURI = false;
+
+ // Note: this is mostly the body of LLVivoxVoiceClient::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(LLVivoxVoiceClient::getInstance()->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 LLVivoxVoiceClient 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 LLVivoxVoiceClient::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;
+}
+
+bool LLVivoxVoiceClient::participantState::isAvatar()
+{
+ return mAvatarIDValid;
+}
+
+void LLVivoxVoiceClient::sessionState::removeParticipant(LLVivoxVoiceClient::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 LLVivoxVoiceClient::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;
+ }
+}
+
+std::vector<LLUUID> LLVivoxVoiceClient::getParticipantList(void)
+{
+ std::vector<LLUUID> result;
+ if(mAudioSession)
+ {
+ for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin();
+ iter != mAudioSession->mParticipantsByUUID.end();
+ iter++)
+ {
+ result.push_back(iter->first);
+ }
+ }
+ return result;
+}
+
+
+LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri)
+{
+ participantState *result = NULL;
+
+ 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;
+}
+
+LLVivoxVoiceClient::participantState* LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id)
+{
+ participantState * result = NULL;
+ participantUUIDMap::iterator iter = mParticipantsByUUID.find(id);
+
+ if(iter != mParticipantsByUUID.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::participantState* LLVivoxVoiceClient::findParticipantByID(const LLUUID& id)
+{
+ participantState * result = NULL;
+
+ if(mAudioSession)
+ {
+ result = mAudioSession->findParticipantByID(id);
+ }
+
+ return result;
+}
+
+
+void LLVivoxVoiceClient::parcelChanged()
+{
+ if(getState() >= stateNoChannel)
+ {
+ // If the user is logged in, start a channel lookup.
+ LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL;
+
+ std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest");
+ LLSD data;
+ LLHTTPClient::post(
+ url,
+ data,
+ new LLVivoxVoiceClientCapResponder);
+ }
+ else
+ {
+ // The transition to stateNoChannel needs to kick this off again.
+ LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::switchChannel(
+ std::string uri,
+ bool spatial,
+ bool no_reconnect,
+ bool is_p2p,
+ std::string hash)
+{
+ bool needsSwitch = false;
+
+ LL_DEBUGS("Voice")
+ << "called in state " << state2string(getState())
+ << " with uri \"" << uri << "\""
+ << (spatial?", spatial is true":", spatial is false")
+ << LL_ENDL;
+
+ switch(getState())
+ {
+ case stateJoinSessionFailed:
+ case stateJoinSessionFailedWaiting:
+ case stateNoChannel:
+ // 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(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(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)
+ {
+ 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)
+ {
+ // We're already set up to join a channel, just needed to fill in the session URI
+ }
+ else
+ {
+ // State machine will come around and rejoin if uri/handle is not empty.
+ sessionTerminate();
+ }
+ }
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::setNonSpatialChannel(
+ const std::string &uri,
+ const std::string &credentials)
+{
+ switchChannel(uri, false, false, false, credentials);
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::callUser(const LLUUID &uuid)
+{
+ std::string userURI = sipURIFromID(uuid);
+
+ switchChannel(userURI, false, true, true);
+}
+
+LLVivoxVoiceClient::sessionState* LLVivoxVoiceClient::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 LLVivoxVoiceClient::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 LLVivoxVoiceClient::sendQueuedTextMessages(sessionState *session)
+{
+ if(session->mTextStreamState == 1)
+ {
+ if(!session->mTextMsgQueue.empty())
+ {
+ std::ostringstream stream;
+
+ while(!session->mTextMsgQueue.empty())
+ {
+ std::string message = session->mTextMsgQueue.front();
+ session->mTextMsgQueue.pop();
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SendMessage.1\">"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<MessageHeader>text/HTML</MessageHeader>"
+ << "<MessageBody>" << message << "</MessageBody>"
+ << "</Request>"
+ << "\n\n\n";
+ }
+ writeString(stream.str());
+ }
+ }
+ else
+ {
+ // Session isn't connected yet, defer until later.
+ }
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::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 LLVivoxVoiceClient::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;
+}
+
+// Returns true if the indicated participant in the current audio session is really an SL avatar.
+// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls.
+BOOL LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id)
+{
+ BOOL result = TRUE;
+ sessionState *session = findSession(id);
+
+ if(session != NULL)
+ {
+ // this is a p2p session with the indicated caller, or the session with the specified UUID.
+ if(session->mSynthesizedCallerID)
+ result = FALSE;
+ }
+ else
+ {
+ // Didn't find a matching session -- check the current audio session for a matching participant
+ if(mAudioSession != NULL)
+ {
+ participantState *participant = findParticipantByID(id);
+ if(participant != NULL)
+ {
+ result = participant->isAvatar();
+ }
+ }
+ }
+
+ return result;
+}
+
+// Returns true if calling back the session URI after the session has closed is possible.
+// Currently this will be false only for PSTN P2P calls.
+BOOL LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id)
+{
+ BOOL result = TRUE;
+ sessionState *session = findSession(session_id);
+
+ if(session != NULL)
+ {
+ result = session->isCallBackPossible();
+ }
+
+ return result;
+}
+
+// Returns true if the session can accepte text IM's.
+// Currently this will be false only for PSTN P2P calls.
+BOOL LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id)
+{
+ bool result = TRUE;
+ sessionState *session = findSession(session_id);
+
+ if(session != NULL)
+ {
+ result = session->isTextIMPossible();
+ }
+
+ return result;
+}
+
+
+void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle)
+{
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ sessionMediaDisconnectSendMessage(session);
+ }
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::getCurrentChannel()
+{
+ std::string result;
+
+ if((getState() == stateRunning) && !mSessionTerminateRequested)
+ {
+ result = getAudioSessionURI();
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::inProximalChannel()
+{
+ bool result = false;
+
+ if((getState() == stateRunning) && !mSessionTerminateRequested)
+ {
+ result = inSpatialChannel();
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id)
+{
+ std::string result;
+ result = "sip:";
+ result += nameFromID(id);
+ result += "@";
+ result += mVoiceSIPURIHostName;
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
+{
+ std::string result;
+ if(avatar)
+ {
+ result = "sip:";
+ result += nameFromID(avatar->getID());
+ result += "@";
+ result += mVoiceSIPURIHostName;
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
+{
+ std::string result;
+ if(avatar)
+ {
+ result = nameFromID(avatar->getID());
+ }
+ return result;
+}
+
+std::string LLVivoxVoiceClient::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 LLVivoxVoiceClient::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 LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
+{
+ return avatar->getFullname();
+}
+
+std::string LLVivoxVoiceClient::sipURIFromName(std::string &name)
+{
+ std::string result;
+ result = "sip:";
+ result += name;
+ result += "@";
+ result += mVoiceSIPURIHostName;
+
+// LLStringUtil::toLower(result);
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::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 LLVivoxVoiceClient::inSpatialChannel(void)
+{
+ bool result = false;
+
+ if(mAudioSession)
+ result = mAudioSession->mIsSpatial;
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::getAudioSessionURI()
+{
+ std::string result;
+
+ if(mAudioSession)
+ result = mAudioSession->mSIPURI;
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::getAudioSessionHandle()
+{
+ std::string result;
+
+ if(mAudioSession)
+ result = mAudioSession->mHandle;
+
+ return result;
+}
+
+
+/////////////////////////////
+// Sending updates of current state
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::updatePosition(void)
+{
+
+ LLVOAvatarSelf *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());
+
+ LLVivoxVoiceClient::getInstance()->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);
+
+ LLVivoxVoiceClient::getInstance()->setAvatarPosition(
+ pos, // position
+ LLVector3::zero, // velocity
+ rot); // rotation matrix
+ }
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::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 LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name)
+{
+ bool result = false;
+
+ if(region)
+ {
+ name = region->getName();
+ }
+
+ if(!name.empty())
+ result = true;
+
+ return result;
+}
+
+void LLVivoxVoiceClient::leaveChannel(void)
+{
+ if(getState() == stateRunning)
+ {
+ LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL;
+ mChannelName.clear();
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::setMuteMic(bool muted)
+{
+ mMuteMic = muted;
+}
+
+void LLVivoxVoiceClient::setUserPTTState(bool ptt)
+{
+ mUserPTTState = ptt;
+}
+
+bool LLVivoxVoiceClient::getUserPTTState()
+{
+ return mUserPTTState;
+}
+
+void LLVivoxVoiceClient::inputUserControlState(bool down)
+{
+ if(mPTTIsToggle)
+ {
+ if(down) // toggle open-mic state on 'down'
+ {
+ toggleUserPTTState();
+ }
+ }
+ else // set open-mic state as an absolute
+ {
+ setUserPTTState(down);
+ }
+}
+
+
+void LLVivoxVoiceClient::toggleUserPTTState(void)
+{
+ mUserPTTState = !mUserPTTState;
+}
+
+void LLVivoxVoiceClient::setVoiceEnabled(bool enabled)
+{
+ if (enabled != mVoiceEnabled)
+ {
+ // TODO: Refactor this so we don't call into LLVoiceChannel, but simply
+ // use the status observer
+ mVoiceEnabled = enabled;
+ LLVoiceClientStatusObserver::EStatusType status;
+
+
+ if (enabled)
+ {
+ LLVoiceChannel::getCurrentVoiceChannel()->activate();
+ status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED;
+ }
+ 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();
+ status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED;
+ }
+ }
+}
+
+bool LLVivoxVoiceClient::voiceEnabled()
+{
+ return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice");
+}
+
+void LLVivoxVoiceClient::setLipSyncEnabled(BOOL enabled)
+{
+ mLipSyncEnabled = enabled;
+}
+
+BOOL LLVivoxVoiceClient::lipSyncEnabled()
+{
+
+ if ( mVoiceEnabled && stateDisabled != getState() )
+ {
+ return mLipSyncEnabled;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+void LLVivoxVoiceClient::setUsePTT(bool usePTT)
+{
+ if(usePTT && !mUsePTT)
+ {
+ // When the user turns on PTT, reset the current state.
+ mUserPTTState = false;
+ }
+ mUsePTT = usePTT;
+}
+
+void LLVivoxVoiceClient::setPTTIsToggle(bool PTTIsToggle)
+{
+ if(!PTTIsToggle && mPTTIsToggle)
+ {
+ // When the user turns off toggle, reset the current state.
+ mUserPTTState = false;
+ }
+
+ mPTTIsToggle = PTTIsToggle;
+}
+
+bool LLVivoxVoiceClient::getPTTIsToggle()
+{
+ return mPTTIsToggle;
+}
+
+void LLVivoxVoiceClient::setPTTKey(std::string &key)
+{
+ if(key == "MiddleMouse")
+ {
+ mPTTIsMiddleMouse = true;
+ }
+ else
+ {
+ mPTTIsMiddleMouse = false;
+ if(!LLKeyboard::keyFromString(key, &mPTTKey))
+ {
+ // If the call failed, don't match any key.
+ key = KEY_NONE;
+ }
+ }
+}
+
+void LLVivoxVoiceClient::setEarLocation(S32 loc)
+{
+ if(mEarLocation != loc)
+ {
+ LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
+
+ mEarLocation = loc;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::setVoiceVolume(F32 volume)
+{
+ 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 LLVivoxVoiceClient::setMicGain(F32 volume)
+{
+ int scaled_volume = scale_mic_volume(volume);
+
+ if(scaled_volume != mMicVolume)
+ {
+ mMicVolume = scaled_volume;
+ mMicVolumeDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::keyDown(KEY key, MASK mask)
+{
+ if (gKeyboard->getKeyRepeated(key))
+ {
+ // ignore auto-repeat keys
+ return;
+ }
+
+ if(!mPTTIsMiddleMouse)
+ {
+ bool down = (mPTTKey != KEY_NONE)
+ && gKeyboard->getKeyDown(mPTTKey);
+ inputUserControlState(down);
+ }
+
+
+}
+void LLVivoxVoiceClient::keyUp(KEY key, MASK mask)
+{
+ if(!mPTTIsMiddleMouse)
+ {
+ bool down = (mPTTKey != KEY_NONE)
+ && gKeyboard->getKeyDown(mPTTKey);
+ inputUserControlState(down);
+ }
+
+}
+void LLVivoxVoiceClient::middleMouseState(bool down)
+{
+ if(mPTTIsMiddleMouse)
+ {
+ if(mPTTIsMiddleMouse)
+ {
+ inputUserControlState(down);
+ }
+ }
+}
+
+/////////////////////////////
+// Accessors for data related to nearby speakers
+BOOL LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id)
+{
+ 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;
+}
+
+std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id)
+{
+ std::string result;
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mDisplayName;
+ }
+
+ return result;
+}
+
+
+
+BOOL LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
+ {
+ participant->mIsSpeaking = FALSE;
+ }
+ result = participant->mIsSpeaking;
+ }
+
+ return result;
+}
+
+BOOL LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mIsModeratorMuted;
+ }
+
+ return result;
+}
+
+F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id)
+{
+ F32 result = 0;
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mPower;
+ }
+
+ return result;
+}
+
+
+
+BOOL LLVivoxVoiceClient::getUsingPTT(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ 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;
+}
+
+BOOL LLVivoxVoiceClient::getOnMuteList(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mOnMuteList;
+ }
+
+ return result;
+}
+
+// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100
+// internal = 400 * external^2
+F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id)
+{
+ F32 result = 0.0f;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ S32 ires = 100; // nominal default volume
+
+ if(participant->mIsSelf)
+ {
+ // Always make it look like the user's own volume is set at the default.
+ }
+ else if(participant->mUserVolume != -1)
+ {
+ // Use the internal volume
+ ires = participant->mUserVolume;
+
+ // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
+// LL_DEBUGS("Voice") << "mapping from mUserVolume " << ires << LL_ENDL;
+ }
+ else if(participant->mVolume != -1)
+ {
+ // Map backwards from vivox volume
+
+ // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
+// LL_DEBUGS("Voice") << "mapping from mVolume " << participant->mVolume << LL_ENDL;
+
+ if(participant->mVolume < 56)
+ {
+ ires = (participant->mVolume * 100) / 56;
+ }
+ else
+ {
+ ires = (((participant->mVolume - 56) * 300) / (100 - 56)) + 100;
+ }
+ }
+ result = sqrtf(((F32)ires) / 400.f);
+ }
+
+ // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
+// LL_DEBUGS("Voice") << "returning " << result << LL_ENDL;
+
+ return result;
+}
+
+void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
+{
+ if(mAudioSession)
+ {
+ participantState *participant = findParticipantByID(id);
+ if (participant)
+ {
+ // volume can amplify by as much as 4x!
+ S32 ivol = (S32)(400.f * volume * volume);
+ participant->mUserVolume = llclamp(ivol, 0, 400);
+ participant->mVolumeDirty = TRUE;
+ mAudioSession->mVolumeDirty = TRUE;
+ }
+ }
+}
+
+std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id)
+{
+ std::string result;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mGroupID;
+ }
+
+ return result;
+}
+
+BOOL LLVivoxVoiceClient::getAreaVoiceDisabled()
+{
+ return mAreaVoiceDisabled;
+}
+
+void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL;
+
+ if(!mMainSessionGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Start</RecordingControlType>"
+ << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>"
+ << "<Filename>" << "" << "</Filename>"
+ << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>"
+ << "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>"
+ << "</Request>\n\n\n";
+
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Flush</RecordingControlType>"
+ << "<Filename>" << filename << "</Filename>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::recordingStop()
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Stop</RecordingControlType>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Start</RecordingControlType>"
+ << "<Filename>" << filename << "</Filename>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::filePlaybackStop()
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Stop</RecordingControlType>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused)
+{
+ // TODO: Implement once Vivox gives me a sample
+}
+
+void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed)
+{
+ // TODO: Implement once Vivox gives me a sample
+}
+
+LLVivoxVoiceClient::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)
+{
+}
+
+LLVivoxVoiceClient::sessionState::~sessionState()
+{
+ removeAllParticipants();
+}
+
+bool LLVivoxVoiceClient::sessionState::isCallBackPossible()
+{
+ // This may change to be explicitly specified by vivox in the future...
+ // Currently, only PSTN P2P calls cannot be returned.
+ // Conveniently, this is also the only case where we synthesize a caller UUID.
+ return !mSynthesizedCallerID;
+}
+
+bool LLVivoxVoiceClient::sessionState::isTextIMPossible()
+{
+ // This may change to be explicitly specified by vivox in the future...
+ return !mSynthesizedCallerID;
+}
+
+
+LLVivoxVoiceClient::sessionIterator LLVivoxVoiceClient::sessionsBegin(void)
+{
+ return mSessions.begin();
+}
+
+LLVivoxVoiceClient::sessionIterator LLVivoxVoiceClient::sessionsEnd(void)
+{
+ return mSessions.end();
+}
+
+
+LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::findSession(const std::string &handle)
+{
+ sessionState *result = NULL;
+ sessionMap::iterator iter = mSessionsByHandle.find(handle);
+ if(iter != mSessionsByHandle.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri)
+{
+ sessionState *result = NULL;
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *session = *iter;
+ if(session->mCreateInProgress && (session->mSIPURI == uri))
+ {
+ result = session;
+ break;
+ }
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::findSession(const LLUUID &participant_id)
+{
+ sessionState *result = NULL;
+
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *session = *iter;
+ if((session->mCallerID == participant_id) || (session->mIMSessionID == participant_id))
+ {
+ result = session;
+ break;
+ }
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle)
+{
+ sessionState *result = NULL;
+
+ if(handle.empty())
+ {
+ // No handle supplied.
+ // Check whether there's already a session with this URI
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *s = *iter;
+ if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri))
+ {
+ // TODO: I need to think about this logic... it's possible that this case should raise an internal error.
+ result = s;
+ break;
+ }
+ }
+ }
+ else // (!handle.empty())
+ {
+ // Check for an existing session with this handle
+ sessionMap::iterator iter = mSessionsByHandle.find(handle);
+
+ if(iter != mSessionsByHandle.end())
+ {
+ result = iter->second;
+ }
+ }
+
+ if(!result)
+ {
+ // No existing session found.
+
+ LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL;
+ result = new sessionState();
+ result->mSIPURI = uri;
+ result->mHandle = handle;
+
+ mSessions.insert(result);
+
+ if(!result->mHandle.empty())
+ {
+ mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result));
+ }
+ }
+ else
+ {
+ // Found an existing session
+
+ if(uri != result->mSIPURI)
+ {
+ // TODO: Should this be an internal error?
+ LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL;
+ setSessionURI(result, uri);
+ }
+
+ if(handle != result->mHandle)
+ {
+ if(handle.empty())
+ {
+ // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break.
+ LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL;
+ }
+ else
+ {
+ // TODO: Should this be an internal error?
+ LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL;
+ setSessionHandle(result, handle);
+ }
+ }
+
+ LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL;
+ }
+
+ verifySessionState();
+
+ return result;
+}
+
+void LLVivoxVoiceClient::setSessionHandle(sessionState *session, const std::string &handle)
+{
+ // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly.
+
+ if(!session->mHandle.empty())
+ {
+ // Remove session from the map if it should have been there.
+ sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle);
+ if(iter != mSessionsByHandle.end())
+ {
+ if(iter->second != session)
+ {
+ LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL;
+ }
+
+ mSessionsByHandle.erase(iter);
+ }
+ else
+ {
+ LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL;
+ }
+ }
+
+ session->mHandle = handle;
+
+ if(!handle.empty())
+ {
+ mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session));
+ }
+
+ verifySessionState();
+}
+
+void LLVivoxVoiceClient::setSessionURI(sessionState *session, const std::string &uri)
+{
+ // There used to be a map of session URIs to sessions, which made this complex....
+ session->mSIPURI = uri;
+
+ verifySessionState();
+}
+
+void LLVivoxVoiceClient::deleteSession(sessionState *session)
+{
+ // Remove the session from the handle map
+ if(!session->mHandle.empty())
+ {
+ sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle);
+ if(iter != mSessionsByHandle.end())
+ {
+ if(iter->second != session)
+ {
+ LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL;
+ }
+ mSessionsByHandle.erase(iter);
+ }
+ }
+
+ // Remove the session from the URI map
+ mSessions.erase(session);
+
+ // At this point, the session should be unhooked from all lists and all state should be consistent.
+ verifySessionState();
+
+ // If this is the current audio session, clean up the pointer which will soon be dangling.
+ if(mAudioSession == session)
+ {
+ mAudioSession = NULL;
+ mAudioSessionChanged = true;
+ }
+
+ // ditto for the next audio session
+ if(mNextAudioSession == session)
+ {
+ mNextAudioSession = NULL;
+ }
+
+ // delete the session
+ delete session;
+}
+
+void LLVivoxVoiceClient::deleteAllSessions()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+
+ while(!mSessions.empty())
+ {
+ deleteSession(*(sessionsBegin()));
+ }
+
+ if(!mSessionsByHandle.empty())
+ {
+ LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::verifySessionState(void)
+{
+ // This is mostly intended for debugging problems with session state management.
+ LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL;
+
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *session = *iter;
+
+ LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL;
+
+ if(!session->mHandle.empty())
+ {
+ // every session with a non-empty handle needs to be in the handle map
+ sessionMap::iterator i2 = mSessionsByHandle.find(session->mHandle);
+ if(i2 == mSessionsByHandle.end())
+ {
+ LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL;
+ }
+ else
+ {
+ if(i2->second != session)
+ {
+ LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL;
+ }
+ }
+ }
+ }
+
+ // check that every entry in the handle map points to a valid session in the session set
+ for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++)
+ {
+ sessionState *session = iter->second;
+ sessionIterator i2 = mSessions.find(session);
+ if(i2 == mSessions.end())
+ {
+ LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL;
+ }
+ else
+ {
+ if(session->mHandle != (*i2)->mHandle)
+ {
+ LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL;
+ }
+ }
+ }
+}
+
+LLVivoxVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) :
+ mURI(uri)
+{
+ mOnlineSL = false;
+ mOnlineSLim = false;
+ mCanSeeMeOnline = true;
+ mHasBlockListEntry = false;
+ mHasAutoAcceptListEntry = false;
+ mNameResolved = false;
+ mInVivoxBuddies = false;
+ mInSLFriends = false;
+ mNeedsNameUpdate = false;
+}
+
+void LLVivoxVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName)
+{
+ buddyListEntry *buddy = addBuddy(uri, displayName);
+ buddy->mInVivoxBuddies = true;
+}
+
+LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::addBuddy(const std::string &uri)
+{
+ std::string empty;
+ buddyListEntry *buddy = addBuddy(uri, empty);
+ if(buddy->mDisplayName.empty())
+ {
+ buddy->mNameResolved = false;
+ }
+ return buddy;
+}
+
+LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::addBuddy(const std::string &uri, const std::string &displayName)
+{
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter = mBuddyListMap.find(uri);
+
+ if(iter != mBuddyListMap.end())
+ {
+ // Found a matching buddy already in the map.
+ LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL;
+ result = iter->second;
+ }
+
+ if(!result)
+ {
+ // participant isn't already in one list or the other.
+ LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL;
+ result = new buddyListEntry(uri);
+ result->mDisplayName = displayName;
+
+ if(IDFromName(uri, result->mUUID))
+ {
+ // Extracted UUID from name successfully.
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL;
+ }
+
+ mBuddyListMap.insert(buddyListMap::value_type(result->mURI, result));
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::findBuddy(const std::string &uri)
+{
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter = mBuddyListMap.find(uri);
+ if(iter != mBuddyListMap.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::findBuddy(const LLUUID &id)
+{
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter;
+
+ for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
+ {
+ if(iter->second->mUUID == id)
+ {
+ result = iter->second;
+ break;
+ }
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::findBuddyByDisplayName(const std::string &name)
+{
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter;
+
+ for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
+ {
+ if(iter->second->mDisplayName == name)
+ {
+ result = iter->second;
+ break;
+ }
+ }
+
+ return result;
+}
+
+void LLVivoxVoiceClient::deleteBuddy(const std::string &uri)
+{
+ buddyListMap::iterator iter = mBuddyListMap.find(uri);
+ if(iter != mBuddyListMap.end())
+ {
+ LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL;
+ buddyListEntry *buddy = iter->second;
+ mBuddyListMap.erase(iter);
+ delete buddy;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL;
+ }
+
+}
+
+void LLVivoxVoiceClient::deleteAllBuddies(void)
+{
+ while(!mBuddyListMap.empty())
+ {
+ deleteBuddy(mBuddyListMap.begin()->first);
+ }
+
+ // Don't want to correlate with friends list when we've emptied the buddy list.
+ mBuddyListMapPopulated = false;
+
+ // Don't want to correlate with friends list when we've reset the block rules.
+ mBlockRulesListReceived = false;
+ mAutoAcceptRulesListReceived = false;
+}
+
+void LLVivoxVoiceClient::deleteAllBlockRules(void)
+{
+ // Clear the block list entry flags from all local buddy list entries
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
+ {
+ buddy_it->second->mHasBlockListEntry = false;
+ }
+}
+
+void LLVivoxVoiceClient::deleteAllAutoAcceptRules(void)
+{
+ // Clear the auto-accept list entry flags from all local buddy list entries
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
+ {
+ buddy_it->second->mHasAutoAcceptListEntry = false;
+ }
+}
+
+void LLVivoxVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly)
+{
+ buddyListEntry *buddy = NULL;
+
+ // blockMask is the SIP URI of a friends list entry
+ buddyListMap::iterator iter = mBuddyListMap.find(blockMask);
+ if(iter != mBuddyListMap.end())
+ {
+ LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL;
+ buddy = iter->second;
+ }
+
+ if(buddy == NULL)
+ {
+ LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL;
+ buddy = addBuddy(blockMask);
+ }
+
+ if(buddy != NULL)
+ {
+ buddy->mHasBlockListEntry = true;
+ }
+}
+
+void LLVivoxVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy)
+{
+ buddyListEntry *buddy = NULL;
+
+ // blockMask is the SIP URI of a friends list entry
+ buddyListMap::iterator iter = mBuddyListMap.find(autoAcceptMask);
+ if(iter != mBuddyListMap.end())
+ {
+ LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL;
+ buddy = iter->second;
+ }
+
+ if(buddy == NULL)
+ {
+ LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL;
+ buddy = addBuddy(autoAcceptMask);
+ }
+
+ if(buddy != NULL)
+ {
+ buddy->mHasAutoAcceptListEntry = true;
+ }
+}
+
+void LLVivoxVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString)
+{
+ // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done.
+ mBlockRulesListReceived = true;
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
+{
+ mParticipantObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
+{
+ mParticipantObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyParticipantObservers()
+{
+ for (observer_set_t::iterator it = mParticipantObservers.begin();
+ it != mParticipantObservers.end();
+ )
+ {
+ LLVoiceClientParticipantObserver* observer = *it;
+ observer->onChange();
+ // In case onChange() deleted an entry.
+ it = mParticipantObservers.upper_bound(observer);
+ }
+}
+
+void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
+{
+ mStatusObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
+{
+ mStatusObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
+{
+ if(mAudioSession)
+ {
+ if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
+ {
+ 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.
+ 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 " << 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, getAudioSessionURI(), inSpatialChannel());
+ // In case onError() deleted an entry.
+ it = mStatusObservers.upper_bound(observer);
+ }
+
+}
+
+void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer)
+{
+ mFriendObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer)
+{
+ mFriendObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::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 LLVivoxVoiceClient::lookupName(const LLUUID &id)
+{
+ BOOL is_group = FALSE;
+ gCacheName->get(id, is_group, &LLVivoxVoiceClient::onAvatarNameLookup);
+}
+
+//static
+void LLVivoxVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group)
+{
+ std::string name = llformat("%s %s", first.c_str(), last.c_str());
+ LLVivoxVoiceClient::getInstance()->avatarNameResolved(id, name);
+
+}
+
+void LLVivoxVoiceClient::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;
+ }
+
+ // 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(
+ LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID),
+ session->mName,
+ session->mCallerID,
+ session->mName,
+ IM_SESSION_P2P_INVITE,
+ LLIMMgr::INVITATION_TYPE_VOICE,
+ session->mHandle);
+ }
+
+ }
+ }
+}
+
+
+LLVivoxProtocolParser::LLVivoxProtocolParser()
+{
+ parser = NULL;
+ parser = XML_ParserCreate(NULL);
+
+ reset();
+}
+
+void LLVivoxProtocolParser::reset()
+{
+ responseDepth = 0;
+ ignoringTags = false;
+ accumulateText = false;
+ energy = 0.f;
+ ignoreDepth = 0;
+ isChannel = false;
+ isEvent = false;
+ isLocallyMuted = false;
+ isModeratorMuted = false;
+ isSpeaking = false;
+ participantType = 0;
+ squelchDebugOutput = false;
+ returnCode = -1;
+ state = 0;
+ statusCode = 0;
+ volume = 0;
+ textBuffer.clear();
+ alias.clear();
+ numberOfAliases = 0;
+ applicationString.clear();
+}
+
+//virtual
+LLVivoxProtocolParser::~LLVivoxProtocolParser()
+{
+ if (parser)
+ XML_ParserFree(parser);
+}
+
+// virtual
+LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
+ const LLChannelDescriptors& channels,
+ buffer_ptr_t& buffer,
+ bool& eos,
+ LLSD& context,
+ LLPumpIO* pump)
+{
+ LLBufferStream istr(channels, buffer.get());
+ std::ostringstream ostr;
+ while (istr.good())
+ {
+ char buf[1024];
+ istr.read(buf, sizeof(buf));
+ mInput.append(buf, istr.gcount());
+ }
+
+ // 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)
+ {
+
+ // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)
+ reset();
+
+ XML_ParserReset(parser, NULL);
+ XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag);
+ XML_SetCharacterDataHandler(parser, ExpatCharHandler);
+ 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("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL;
+
+ if(!LLVivoxVoiceClient::getInstance()->mConnected)
+ {
+ // If voice has been disabled, we just want to close the socket. This does so.
+ LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL;
+ return STATUS_STOP;
+ }
+
+ return STATUS_OK;
+}
+
+void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr)
+{
+ if (data)
+ {
+ LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
+ object->StartTag(el, attr);
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el)
+{
+ if (data)
+ {
+ LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
+ object->EndTag(el);
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len)
+{
+ if (data)
+ {
+ LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
+ object->CharData(s, len);
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+
+void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
+{
+ // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags
+ textBuffer.clear();
+ // only accumulate text if we're not ignoring tags.
+ accumulateText = !ignoringTags;
+
+ if (responseDepth == 0)
+ {
+ isEvent = !stricmp("Event", tag);
+
+ if (!stricmp("Response", tag) || isEvent)
+ {
+ // Grab the attributes
+ while (*attr)
+ {
+ const char *key = *attr++;
+ const char *value = *attr++;
+
+ if (!stricmp("requestId", key))
+ {
+ requestId = value;
+ }
+ else if (!stricmp("action", key))
+ {
+ actionString = value;
+ }
+ else if (!stricmp("type", key))
+ {
+ eventTypeString = value;
+ }
+ }
+ }
+ LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
+ }
+ else
+ {
+ if (ignoringTags)
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
+
+ // Ignore the InputXml stuff so we don't get confused
+ if (!stricmp("InputXml", tag))
+ {
+ ignoringTags = true;
+ ignoreDepth = responseDepth;
+ accumulateText = false;
+
+ LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
+ }
+ else if (!stricmp("CaptureDevices", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->clearCaptureDevices();
+ }
+ else if (!stricmp("RenderDevices", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->clearRenderDevices();
+ }
+ else if (!stricmp("CaptureDevice", tag))
+ {
+ deviceString.clear();
+ }
+ else if (!stricmp("RenderDevice", tag))
+ {
+ deviceString.clear();
+ }
+ else if (!stricmp("Buddies", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->deleteAllBuddies();
+ }
+ else if (!stricmp("BlockRules", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->deleteAllBlockRules();
+ }
+ else if (!stricmp("AutoAcceptRules", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->deleteAllAutoAcceptRules();
+ }
+
+ }
+ }
+ responseDepth++;
+}
+
+// --------------------------------------------------------------------------------
+
+void LLVivoxProtocolParser::EndTag(const char *tag)
+{
+ const std::string& string = textBuffer;
+
+ responseDepth--;
+
+ if (ignoringTags)
+ {
+ if (ignoreDepth == responseDepth)
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL;
+ ignoringTags = false;
+ }
+ else
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ }
+ }
+
+ if (!ignoringTags)
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+
+ // Closing a tag. Finalize the text we've accumulated and reset
+ if (!stricmp("ReturnCode", tag))
+ returnCode = strtol(string.c_str(), NULL, 10);
+ 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 (!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 (!stricmp("VersionID", tag))
+ versionID = string;
+ else if (!stricmp("AccountHandle", tag))
+ accountHandle = string;
+ else if (!stricmp("State", tag))
+ state = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("URI", tag))
+ uriString = string;
+ 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 (!stricmp("AudioMedia", tag))
+ audioMediaString = string;
+ else if (!stricmp("ChannelName", tag))
+ nameString = string;
+ else if (!stricmp("DisplayName", tag))
+ displayNameString = string;
+ else if (!stricmp("Device", tag))
+ deviceString = string;
+ else if (!stricmp("AccountName", tag))
+ nameString = string;
+ else if (!stricmp("ParticipantType", tag))
+ participantType = strtol(string.c_str(), NULL, 10);
+ 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 (!stricmp("ChannelName", tag))
+ nameString = string;
+ else if (!stricmp("ChannelURI", tag))
+ uriString = string;
+ else if (!stricmp("BuddyURI", tag))
+ uriString = string;
+ else if (!stricmp("Presence", tag))
+ statusString = string;
+ else if (!stricmp("CaptureDevice", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addCaptureDevice(deviceString);
+ }
+ else if (!stricmp("RenderDevice", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addRenderDevice(deviceString);
+ }
+ else if (!stricmp("Buddy", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->processBuddyListEntry(uriString, displayNameString);
+ }
+ else if (!stricmp("BlockRule", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addBlockRule(blockMask, presenceOnly);
+ }
+ else if (!stricmp("BlockMask", tag))
+ blockMask = string;
+ else if (!stricmp("PresenceOnly", tag))
+ presenceOnly = string;
+ else if (!stricmp("AutoAcceptRule", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->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;
+
+
+ textBuffer.clear();
+ accumulateText= false;
+
+ if (responseDepth == 0)
+ {
+ // We finished all of the XML, process the data
+ processResponse(tag);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+void LLVivoxProtocolParser::CharData(const char *buffer, int length)
+{
+ /*
+ This method is called for anything that isn't a tag, which can be text you
+ want that lies between tags, and a lot of stuff you don't want like file formatting
+ (tabs, spaces, CR/LF, etc).
+
+ Only copy text if we are in accumulate mode...
+ */
+ if (accumulateText)
+ textBuffer.append(buffer, length);
+}
+
+// --------------------------------------------------------------------------------
+
+void LLVivoxProtocolParser::processResponse(std::string tag)
+{
+ 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)
+ {
+ const char *eventTypeCstr = eventTypeString.c_str();
+ if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionAddedEvent"))
+ {
+ /*
+ <Event type="SessionAddedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <Uri>sip:confctl-1408789@bhr.vivox.com</Uri>
+ <IsChannel>true</IsChannel>
+ <Incoming>false</Incoming>
+ <ChannelName />
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionRemovedEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle);
+ }
+ else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent"))
+ {
+ /*
+ <Event type="MediaStreamUpdatedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <StatusCode>200</StatusCode>
+ <StatusString>OK</StatusString>
+ <State>2</State>
+ <Incoming>false</Incoming>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
+ }
+ else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent"))
+ {
+ /*
+ <Event type="TextStreamUpdatedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==1</SessionHandle>
+ <Enabled>true</Enabled>
+ <State>1</State>
+ <Incoming>true</Incoming>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent"))
+ {
+ /*
+ <Event type="ParticipantAddedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
+ <ParticipantUri>sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com</ParticipantUri>
+ <AccountName>xI5auBZ60SJWIk606-1JGRQ==</AccountName>
+ <DisplayName />
+ <ParticipantType>0</ParticipantType>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent"))
+ {
+ /*
+ <Event type="ParticipantRemovedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
+ <ParticipantUri>sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com</ParticipantUri>
+ <AccountName>xtx7YNV-3SGiG7rA1fo5Ndw==</AccountName>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent"))
+ {
+ /*
+ <Event type="ParticipantUpdatedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <ParticipantUri>sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com</ParticipantUri>
+ <IsModeratorMuted>false</IsModeratorMuted>
+ <IsSpeaking>true</IsSpeaking>
+ <Volume>44</Volume>
+ <Energy>0.0879437</Energy>
+ </Event>
+ */
+
+ // These happen so often that logging them is pretty useless.
+ squelchDebugOutput = true;
+
+ LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy);
+ }
+ else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy);
+ }
+ else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->buddyPresenceEvent(uriString, alias, statusString, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent"))
+ {
+ // The buddy list was updated during parsing.
+ // Need to recheck against the friends list.
+ LLVivoxVoiceClient::getInstance()->buddyListChanged();
+ }
+ else if (!stricmp(eventTypeCstr, "BuddyChangedEvent"))
+ {
+ /*
+ <Event type="BuddyChangedEvent">
+ <AccountHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==</AccountHandle>
+ <BuddyURI>sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com</BuddyURI>
+ <DisplayName>Monroe Tester</DisplayName>
+ <BuddyData />
+ <GroupID>0</GroupID>
+ <ChangeType>Set</ChangeType>
+ </Event>
+ */
+ // TODO: Question: Do we need to process this at all?
+ }
+ else if (!stricmp(eventTypeCstr, "MessageEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionNotificationEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType);
+ }
+ else if (!stricmp(eventTypeCstr, "SubscriptionEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent"))
+ {
+ /*
+ <Event type="SessionUpdatedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <Uri>sip:confctl-9@bhd.vivox.com</Uri>
+ <IsMuted>0</IsMuted>
+ <Volume>50</Volume>
+ <TransmitEnabled>1</TransmitEnabled>
+ <IsFocused>0</IsFocused>
+ <SpeakerPosition><Position><X>0</X><Y>0</Y><Z>0</Z></Position></SpeakerPosition>
+ <SessionFontID>0</SessionFontID>
+ </Event>
+ */
+ // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
+ }
+
+ else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent"))
+ {
+ /*
+ <Event type="SessionGroupRemovedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ </Event>
+ */
+ // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
+ }
+ else
+ {
+ LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL;
+ }
+ }
+ else
+ {
+ const char *actionCstr = actionString.c_str();
+ if (!stricmp(actionCstr, "Connector.Create.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID);
+ }
+ else if (!stricmp(actionCstr, "Account.Login.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases);
+ }
+ else if (!stricmp(actionCstr, "Session.Create.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle);
+ }
+ else if (!stricmp(actionCstr, "SessionGroup.AddSession.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle);
+ }
+ else if (!stricmp(actionCstr, "Session.Connect.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.Logout.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.ListBlockRules.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountListBlockRulesResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.ListAutoAcceptRules.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->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 (!stricmp(actionCstr, "Account.ChannelGetList.1"))
+ {
+ LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Connector.AccountCreate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelCreate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelUpdate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelDelete.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1"))
+ {
+
+ }
+ */
+ }
+}
+
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
new file mode 100644
index 0000000000..5bf13e6793
--- /dev/null
+++ b/indra/newview/llvoicevivox.h
@@ -0,0 +1,905 @@
+/**
+ * @file llvoicevivox.h
+ * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewergpl$
+ *
+ * Copyright (c) 2001-2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+#ifndef LL_VOICE_VIVOX_H
+#define LL_VOICE_VIVOX_H
+
+class LLVOAvatar;
+class LLVivoxProtocolParser;
+
+#include "lliopipe.h"
+#include "llpumpio.h"
+#include "llchainio.h"
+#include "lliosocket.h"
+#include "v3math.h"
+#include "llframetimer.h"
+#include "llviewerregion.h"
+#include "llcallingcard.h" // for LLFriendObserver
+
+#ifdef LL_STANDALONE
+# include "expat.h"
+#else
+# include "expat/expat.h"
+#endif
+#include "llvoiceclient.h"
+
+
+class LLVivoxVoiceAccountProvisionResponder;
+class LLVivoxVoiceClientMuteListObserver;
+class LLVivoxVoiceClientFriendsObserver;
+
+
+class LLVivoxVoiceClientParticipantObserver
+{
+public:
+ virtual ~LLVivoxVoiceClientParticipantObserver() { }
+ virtual void onChange() = 0;
+};
+
+
+class LLVivoxVoiceClient: public LLSingleton<LLVivoxVoiceClient>, virtual public LLVoiceModuleInterface
+{
+ LOG_CLASS(LLVivoxVoiceClient);
+public:
+ LLVivoxVoiceClient();
+ virtual ~LLVivoxVoiceClient();
+
+
+ /// @name LLVoiceModuleInterface virtual implementations
+ /// @see LLVoiceModuleInterface
+ //@{
+ virtual void init(LLPumpIO *pump); // Call this once at application startup (creates connector)
+ virtual void terminate(); // Call this to clean up during shutdown
+
+ virtual const LLVoiceVersionInfo& getVersion();
+
+ virtual void updateSettings(); // call after loading settings and whenever they change
+
+ /////////////////////
+ /// @name Tuning
+ //@{
+ virtual void tuningStart();
+ virtual void tuningStop();
+ virtual bool inTuningMode();
+
+ virtual void tuningSetMicVolume(float volume);
+ virtual void tuningSetSpeakerVolume(float volume);
+ virtual float tuningGetEnergy(void);
+ //@}
+
+ /////////////////////
+ /// @name Devices
+ //@{
+ // This returns true when it's safe to bring up the "device settings" dialog in the prefs.
+ // i.e. when the daemon is running and connected, and the device lists are populated.
+ virtual bool deviceSettingsAvailable();
+
+ // Requery the vivox daemon for the current list of input/output devices.
+ // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed
+ // (use this if you want to know when it's done).
+ // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim.
+ virtual void refreshDeviceLists(bool clearCurrentList = true);
+
+ virtual void setCaptureDevice(const std::string& name);
+ virtual void setRenderDevice(const std::string& name);
+
+ virtual LLVoiceDeviceList& getCaptureDevices();
+ virtual LLVoiceDeviceList& getRenderDevices();
+ //@}
+
+ virtual std::vector<LLUUID> getParticipantList(void);
+ // Send a text message to the specified user, initiating the session if necessary.
+ virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message);
+
+ // close any existing text IM session with the specified user
+ virtual void endUserIMSession(const LLUUID &uuid);
+
+ // Returns true if calling back the session URI after the session has closed is possible.
+ // Currently this will be false only for PSTN P2P calls.
+ // NOTE: this will return true if the session can't be found.
+ virtual BOOL isSessionCallBackPossible(const LLUUID &session_id);
+
+ // Returns true if the session can accepte text IM's.
+ // Currently this will be false only for PSTN P2P calls.
+ // NOTE: this will return true if the session can't be found.
+ virtual BOOL isSessionTextIMPossible(const LLUUID &session_id);
+
+
+ ////////////////////////////
+ /// @name Channel stuff
+ //@{
+ // returns true iff the user is currently in a proximal (local spatial) channel.
+ // Note that gestures should only fire if this returns true.
+ virtual bool inProximalChannel();
+
+ virtual void setNonSpatialChannel(const std::string &uri,
+ const std::string &credentials);
+
+ virtual void setSpatialChannel(const std::string &uri,
+ const std::string &credentials);
+
+ virtual void leaveNonSpatialChannel();
+
+ virtual void leaveChannel(void);
+
+ // Returns the URI of the current channel, or an empty string if not currently in a channel.
+ // NOTE that it will return an empty string if it's in the process of joining a channel.
+ virtual std::string getCurrentChannel();
+ //@}
+
+
+ //////////////////////////
+ /// @name invitations
+ //@{
+ // start a voice channel with the specified user
+ virtual void callUser(const LLUUID &uuid);
+ virtual bool answerInvite(std::string &channelHandle);
+ virtual void declineInvite(std::string &channelHandle);
+ //@}
+
+ /////////////////////////
+ /// @name Volume/gain
+ //@{
+ virtual void setVoiceVolume(F32 volume);
+ virtual void setMicGain(F32 volume);
+ //@}
+
+ /////////////////////////
+ /// @name enable disable voice and features
+ //@{
+ virtual bool voiceEnabled();
+ virtual void setVoiceEnabled(bool enabled);
+ virtual BOOL lipSyncEnabled();
+ virtual void setLipSyncEnabled(BOOL enabled);
+ virtual void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state.
+ //@}
+
+ ////////////////////////
+ /// @name PTT
+ //@{
+ virtual void setUserPTTState(bool ptt);
+ virtual bool getUserPTTState();
+ virtual void setUsePTT(bool usePTT);
+ virtual void setPTTIsToggle(bool PTTIsToggle);
+ virtual bool getPTTIsToggle();
+ virtual void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs
+ virtual void toggleUserPTTState(void);
+
+ virtual void keyDown(KEY key, MASK mask);
+ virtual void keyUp(KEY key, MASK mask);
+ virtual void middleMouseState(bool down);
+ //@}
+
+ //////////////////////////
+ /// @name nearby speaker accessors
+ //@{
+ virtual BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar
+ virtual std::string getDisplayName(const LLUUID& id);
+ virtual BOOL isOnlineSIP(const LLUUID &id);
+ virtual BOOL isParticipantAvatar(const LLUUID &id);
+ virtual BOOL getIsSpeaking(const LLUUID& id);
+ virtual BOOL getIsModeratorMuted(const LLUUID& id);
+ virtual F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
+ virtual BOOL getOnMuteList(const LLUUID& id);
+ virtual F32 getUserVolume(const LLUUID& id);
+ virtual void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal)
+ //@}
+
+ // authorize the user
+ virtual void userAuthorized(const std::string& user_id,
+ const LLUUID &agentID);
+
+ //////////////////////////////
+ /// @name Status notification
+ //@{
+ virtual void addObserver(LLVoiceClientStatusObserver* observer);
+ virtual void removeObserver(LLVoiceClientStatusObserver* observer);
+ virtual void addObserver(LLFriendObserver* observer);
+ virtual void removeObserver(LLFriendObserver* observer);
+ virtual void addObserver(LLVoiceClientParticipantObserver* observer);
+ virtual void removeObserver(LLVoiceClientParticipantObserver* observer);
+
+
+
+ //@}
+
+ virtual std::string sipURIFromID(const LLUUID &id);
+ //@}
+
+
+protected:
+ //////////////////////
+ // Vivox Specific definitions
+
+ friend class LLVivoxVoiceAccountProvisionResponder;
+ friend class LLVivoxVoiceClientMuteListObserver;
+ friend class LLVivoxVoiceClientFriendsObserver;
+
+ enum streamState
+ {
+ streamStateUnknown = 0,
+ streamStateIdle = 1,
+ streamStateConnected = 2,
+ streamStateRinging = 3,
+ };
+ struct participantState
+ {
+ public:
+ participantState(const std::string &uri);
+
+ bool updateMuteState();
+ bool isAvatar();
+
+ std::string mURI;
+ LLUUID mAvatarID;
+ std::string mAccountName;
+ std::string mDisplayName;
+ LLFrameTimer mSpeakingTimeout;
+ F32 mLastSpokeTimestamp;
+ F32 mPower;
+ int mVolume;
+ std::string mGroupID;
+ 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;
+ bool mIsSelf;
+ };
+
+ typedef std::map<const std::string, participantState*> participantMap;
+
+ typedef std::map<const LLUUID, participantState*> participantUUIDMap;
+
+ 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);
+
+ bool isCallBackPossible();
+ bool isTextIMPossible();
+
+ std::string mHandle;
+ std::string mGroupHandle;
+ std::string mSIPURI;
+ std::string mAlias;
+ std::string mName;
+ std::string mAlternateSIPURI;
+ std::string mHash; // Channel password
+ std::string mErrorStatusString;
+ std::queue<std::string> mTextMsgQueue;
+
+ LLUUID mIMSessionID;
+ LLUUID mCallerID;
+ int mErrorStatusCode;
+ int mMediaStreamState;
+ int mTextStreamState;
+ bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet.
+ bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet.
+ bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup)
+ bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup)
+ bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup.
+ bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN)
+ bool mIsSpatial; // True for spatial channels
+ bool mIsP2P;
+ bool mIncoming;
+ bool mVoiceEnabled;
+ bool mReconnect; // Whether we should try to reconnect to this session if it's dropped
+ // Set to true when the mute state of someone in the participant list changes.
+ // The code will have to walk the list to find the changed participant(s).
+ bool mVolumeDirty;
+
+ bool mParticipantsChanged;
+ participantMap mParticipantsByURI;
+ participantUUIDMap mParticipantsByUUID;
+ };
+
+ // 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 LLVivoxVoiceClient::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
+ 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, //
+ stateJoiningSession, // waiting for session handle
+ stateSessionJoined, // session handle received
+ stateRunning, // in session, steady state
+ stateLeavingSession, // waiting for terminate session response
+ stateSessionTerminated, // waiting for terminate session response
+
+ stateLoggingOut, // waiting for logout response
+ stateLoggedOut, // logout response received
+ stateConnectorStopping, // waiting for connector stop
+ stateConnectorStopped, // connector stop received
+
+ // We go to this state if the login fails because the account needs to be provisioned.
+
+ // error states. No way to recover from these yet.
+ stateConnectorFailed,
+ stateConnectorFailedWaiting,
+ stateLoginFailed,
+ stateLoginFailedWaiting,
+ stateJoinSessionFailed,
+ stateJoinSessionFailedWaiting,
+
+ stateJail // Go here when all else has failed. Nothing will be retried, we're done.
+ };
+
+ typedef std::map<std::string, sessionState*> sessionMap;
+
+
+
+ ///////////////////////////////////////////////////////
+ // Private Member Functions
+ //////////////////////////////////////////////////////
+
+ //////////////////////////////
+ /// @name TVC/Server management and communication
+ //@{
+ // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch.
+ void daemonDied();
+
+ // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away.
+ void giveUp();
+
+ // write to the tvc
+ bool writeString(const std::string &str);
+
+ void connectorCreate();
+ void connectorShutdown();
+ void closeSocket(void);
+
+ void requestVoiceAccountProvision(S32 retries = 3);
+ 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();
+
+
+ //@}
+
+ //------------------------------------
+ // tuning
+
+ void tuningRenderStartSendMessage(const std::string& name, bool loop);
+ void tuningRenderStopSendMessage();
+
+ void tuningCaptureStartSendMessage(int duration);
+ void tuningCaptureStopSendMessage();
+
+ bool inTuningStates();
+
+ //----------------------------------
+ // devices
+ void clearCaptureDevices();
+ void addCaptureDevice(const std::string& name);
+ void clearRenderDevices();
+ void addRenderDevice(const std::string& name);
+ void buildSetAudioDevices(std::ostringstream &stream);
+
+ void getCaptureDevicesSendMessage();
+ void getRenderDevicesSendMessage();
+
+ // local audio updates
+ void buildLocalAudioUpdates(std::ostringstream &stream);
+
+
+ /////////////////////////////
+ // Response/Event handlers
+ 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 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 &notificationType);
+ 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
+ void updatePosition(void);
+ void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot);
+ void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot);
+ bool channelFromRegion(LLViewerRegion *region, std::string &name);
+
+ void setEarLocation(S32 loc);
+
+
+ /////////////////////////////
+ // Accessors for data related to nearby speakers
+
+ // MBW -- XXX -- Not sure how to get this data out of the TVC
+ BOOL getUsingPTT(const LLUUID& id);
+ 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.
+
+
+ // PTT
+ void setPTTKey(std::string &key);
+
+ /////////////////////////////
+ // 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);
+
+ participantState *findParticipantByID(const LLUUID& id);
+
+
+ ////////////////////////////////////////
+ // voice sessions.
+ typedef std::set<sessionState*> sessionSet;
+
+ typedef sessionSet::iterator sessionIterator;
+ sessionIterator sessionsBegin(void);
+ sessionIterator sessionsEnd(void);
+
+ sessionState *findSession(const std::string &handle);
+ sessionState *findSessionBeingCreatedByURI(const std::string &uri);
+ sessionState *findSession(const LLUUID &participant_id);
+ sessionState *findSessionByCreateID(const std::string &create_id);
+
+ sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null);
+ void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null);
+ void setSessionURI(sessionState *session, const std::string &uri);
+ void deleteSession(sessionState *session);
+ void deleteAllSessions(void);
+
+ void verifySessionState(void);
+
+ void joinedAudioSession(sessionState *session);
+ void leftAudioSession(sessionState *session);
+
+ // This is called in several places where the session _may_ need to be deleted.
+ // It contains logic for whether to delete the session or keep it around.
+ void reapSession(sessionState *session);
+
+ // Returns true if the session seems to indicate we've moved to a region on a different voice server
+ bool sessionNeedsRelog(sessionState *session);
+
+
+ //////////////////////////////////////
+ // buddy list stuff, needed for SLIM later
+ 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<std::string, buddyListEntry*> 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 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 sessionGroupTerminateSendMessage(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 lookupName(const LLUUID &id);
+ static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group);
+ void avatarNameResolved(const LLUUID &id, const std::string &name);
+
+private:
+ LLVoiceVersionInfo mVoiceVersion;
+
+ state mState;
+ bool mSessionTerminateRequested;
+ bool mRelogRequested;
+
+ void setState(state inState);
+ state getState(void) { return mState; };
+ std::string state2string(state inState);
+
+ void stateMachine();
+ static void idle(void *user_data);
+
+ LLHost mDaemonHost;
+ LLSocket::ptr_t mSocket;
+ bool mConnected;
+
+
+ LLPumpIO *mPump;
+ friend class LLVivoxProtocolParser;
+
+ std::string mAccountName;
+ std::string mAccountPassword;
+ std::string mAccountDisplayName;
+
+ bool mTuningMode;
+ float mTuningEnergy;
+ std::string mTuningAudioFile;
+ int mTuningMicVolume;
+ bool mTuningMicVolumeDirty;
+ int mTuningSpeakerVolume;
+ bool mTuningSpeakerVolumeDirty;
+ state mTuningExitState; // state to return to when we leave tuning mode.
+
+ std::string mSpatialSessionURI;
+ 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;
+ 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
+ int mNumberOfAliases;
+ U32 mCommandCookie;
+
+ std::string mVoiceAccountServerURI;
+ std::string mVoiceSIPURIHostName;
+
+ int mLoginRetryCount;
+
+ 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;
+
+ LLVoiceDeviceList mCaptureDevices;
+ LLVoiceDeviceList mRenderDevices;
+
+ std::string mCaptureDevice;
+ std::string mRenderDevice;
+ bool mCaptureDeviceDirty;
+ bool mRenderDeviceDirty;
+
+ // 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 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);
+ 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.
+ 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 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);
+
+ bool mSpatialCoordsDirty;
+
+ LLVector3d mCameraPosition;
+ LLVector3d mCameraRequestedPosition;
+ LLVector3 mCameraVelocity;
+ LLMatrix3 mCameraRot;
+
+ LLVector3d mAvatarPosition;
+ LLVector3 mAvatarVelocity;
+ LLMatrix3 mAvatarRot;
+
+ bool mPTTDirty;
+ bool mPTT;
+
+ bool mUsePTT;
+ bool mPTTIsMiddleMouse;
+ KEY mPTTKey;
+ bool mPTTIsToggle;
+ bool mUserPTTState;
+ bool mMuteMic;
+
+ // Set to true when the friends list is known to have changed.
+ bool mFriendsListDirty;
+
+ enum
+ {
+ earLocCamera = 0, // ear at camera
+ earLocAvatar, // ear at avatar
+ earLocMixed // ear at avatar location/camera direction
+ };
+
+ S32 mEarLocation;
+
+ bool mSpeakerVolumeDirty;
+ bool mSpeakerMuteDirty;
+ int mSpeakerVolume;
+
+ int mMicVolume;
+ bool mMicVolumeDirty;
+
+ bool mVoiceEnabled;
+ bool mWriteInProgress;
+ std::string mWriteString;
+ size_t mWriteOffset;
+
+ LLTimer mUpdateTimer;
+
+ BOOL mLipSyncEnabled;
+
+ typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t;
+ observer_set_t mParticipantObservers;
+
+ void notifyParticipantObservers();
+
+ typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t;
+ status_observer_set_t mStatusObservers;
+
+ void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status);
+
+ typedef std::set<LLFriendObserver*> friend_observer_set_t;
+ friend_observer_set_t mFriendObservers;
+ void notifyFriendObservers();
+};
+
+/**
+ * @class LLVivoxProtocolParser
+ * @brief This class helps construct new LLIOPipe specializations
+ * @see LLIOPipe
+ *
+ * THOROUGH_DESCRIPTION
+ */
+class LLVivoxProtocolParser : public LLIOPipe
+{
+ LOG_CLASS(LLVivoxProtocolParser);
+public:
+ LLVivoxProtocolParser();
+ virtual ~LLVivoxProtocolParser();
+
+protected:
+ /* @name LLIOPipe virtual implementations
+ */
+ //@{
+ /**
+ * @brief Process the data in buffer
+ */
+ virtual EStatus process_impl(
+ const LLChannelDescriptors& channels,
+ buffer_ptr_t& buffer,
+ bool& eos,
+ LLSD& context,
+ LLPumpIO* pump);
+ //@}
+
+ std::string mInput;
+
+ // Expat control members
+ XML_Parser parser;
+ int responseDepth;
+ bool ignoringTags;
+ bool isEvent;
+ 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 requestId;
+ std::string actionString;
+ std::string connectorHandle;
+ std::string versionID;
+ std::string accountHandle;
+ std::string sessionHandle;
+ 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 deviceString;
+ std::string displayNameString;
+ int participantType;
+ bool isLocallyMuted;
+ bool isModeratorMuted;
+ 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;
+ bool accumulateText;
+
+ void reset();
+
+ void processResponse(std::string tag);
+
+ static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr);
+ static void XMLCALL ExpatEndTag(void *data, const char *el);
+ static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len);
+
+ void StartTag(const char *tag, const char **attr);
+ void EndTag(const char *tag);
+ void CharData(const char *buffer, int length);
+
+};
+
+
+#endif //LL_VIVOX_VOICE_CLIENT_H
+
+
+
diff --git a/indra/newview/skins/default/xui/en/floater_about.xml b/indra/newview/skins/default/xui/en/floater_about.xml
index fac7aef690..cb5cec7bdb 100644
--- a/indra/newview/skins/default/xui/en/floater_about.xml
+++ b/indra/newview/skins/default/xui/en/floater_about.xml
@@ -49,7 +49,7 @@ libcurl Version: [LIBCURL_VERSION]
J2C Decoder Version: [J2C_VERSION]
Audio Driver Version: [AUDIO_DRIVER_VERSION]
Qt Webkit Version: [QT_WEBKIT_VERSION]
-Vivox Version: [VIVOX_VERSION]
+Voice Server Version: [VOICE_VERSION]
</floater.string>
<floater.string
name="none">
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 0db18525d7..1917fd9b5c 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -397,6 +397,15 @@ class WindowsManifest(ViewerManifest):
self.disable_manifest_check()
+ # Diamondware Runtimes
+ if self.prefix(src="diamondware-runtime/i686-win32", dst=""):
+ self.path("SLVoice_dwTVC.exe")
+ self.path("libcurl.dll")
+ self.path("libeay32.dll")
+ self.path("ssleay32.dll")
+ self.path("zlib1.dll")
+ self.end_prefix()
+
# pull in the crash logger and updater from other projects
# tag:"crash-logger" here as a cue to the exporter
self.path(src='../win_crash_logger/%s/windows-crash-logger.exe' % self.args['configuration'],
@@ -606,6 +615,9 @@ class DarwinManifest(ViewerManifest):
self.path("vivox-runtime/universal-darwin/libvivoxsdk.dylib", "libvivoxsdk.dylib")
self.path("vivox-runtime/universal-darwin/libvivoxplatform.dylib", "libvivoxplatform.dylib")
self.path("vivox-runtime/universal-darwin/SLVoice", "SLVoice")
+ # DiamondWare runtime
+ self.path("diamondware-runtime/universal-darwin/SLVoice_dwTVC","SLVoice_dwTVC")
+ self.path("diamondware-runtime/universal-darwin/libfmodex.dylib", "libfmodex.dylib")
libdir = "../../libraries/universal-darwin/lib_release"
dylibs = {}
@@ -898,6 +910,11 @@ class Linux_i686Manifest(LinuxManifest):
pass
self.end_prefix("lib")
+ # Diamondware runtimes
+ if self.prefix(src="diamondware-runtime/i686-linux", dst="bin"):
+ self.path("SLVoice_dwTVC")
+ self.end_prefix()
+
# Vivox runtimes
if self.prefix(src="vivox-runtime/i686-linux", dst="bin"):
self.path("SLVoice")
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
index 452930a3b3..8af7453bd6 100644
--- a/indra/viewer_components/login/lllogin.cpp
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -131,6 +131,7 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)
std::string coroname =
LLCoros::instance().launch("LLLogin::Impl::login_",
boost::bind(&Impl::login_, this, _1, uri, login_params));
+ LL_DEBUGS("LLLogin") << " connected with uri '" << uri << "', login_params " << login_params << LL_ENDL;
}
void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD login_params)