summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/CMakeLists.txt4
-rw-r--r--indra/newview/app_settings/logcontrol.xml2
-rw-r--r--indra/newview/app_settings/settings.xml22
-rw-r--r--indra/newview/app_settings/settings_per_account.xml11
-rw-r--r--indra/newview/llcallfloater.cpp23
-rw-r--r--indra/newview/llcallfloater.h18
-rw-r--r--indra/newview/llfloatervoiceeffect.cpp288
-rw-r--r--indra/newview/llfloatervoiceeffect.h78
-rw-r--r--indra/newview/llpanelvoiceeffect.cpp169
-rw-r--r--indra/newview/llpanelvoiceeffect.h73
-rw-r--r--indra/newview/llparticipantlist.cpp2
-rw-r--r--indra/newview/llspeakingindicatormanager.cpp4
-rw-r--r--indra/newview/lltexlayer.cpp116
-rw-r--r--indra/newview/lltexturefetch.cpp20
-rw-r--r--indra/newview/llviewerfloaterreg.cpp4
-rw-r--r--indra/newview/llvoicechannel.cpp6
-rw-r--r--indra/newview/llvoicechannel.h2
-rw-r--r--indra/newview/llvoiceclient.cpp45
-rw-r--r--indra/newview/llvoiceclient.h81
-rw-r--r--indra/newview/llvoicevivox.cpp1016
-rw-r--r--indra/newview/llvoicevivox.h192
-rw-r--r--indra/newview/skins/default/xui/en/floater_voice_controls.xml53
-rw-r--r--indra/newview/skins/default/xui/en/floater_voice_effect.xml101
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml11
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml44
-rw-r--r--indra/newview/skins/default/xui/en/panel_voice_effect.xml33
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml2
27 files changed, 2222 insertions, 198 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7dbe650625..035e9ddd9c 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -215,6 +215,7 @@ set(viewer_SOURCE_FILES
llfloaterurldisplay.cpp
llfloaterurlentry.cpp
llfloatervoicedevicesettings.cpp
+ llfloatervoiceeffect.cpp
llfloaterwater.cpp
llfloaterwhitelistentry.cpp
llfloaterwindlight.cpp
@@ -356,6 +357,7 @@ set(viewer_SOURCE_FILES
llpanelprofileview.cpp
llpanelteleporthistory.cpp
llpaneltiptoast.cpp
+ llpanelvoiceeffect.cpp
llpaneltopinfobar.cpp
llpanelvolume.cpp
llpanelvolumepulldown.cpp
@@ -736,6 +738,7 @@ set(viewer_HEADER_FILES
llfloaterurldisplay.h
llfloaterurlentry.h
llfloatervoicedevicesettings.h
+ llfloatervoiceeffect.h
llfloaterwater.h
llfloaterwhitelistentry.h
llfloaterwindlight.h
@@ -872,6 +875,7 @@ set(viewer_HEADER_FILES
llpanelprofileview.h
llpanelteleporthistory.h
llpaneltiptoast.h
+ llpanelvoiceeffect.h
llpaneltopinfobar.h
llpanelvolume.h
llpanelvolumepulldown.h
diff --git a/indra/newview/app_settings/logcontrol.xml b/indra/newview/app_settings/logcontrol.xml
index 16e39fc1c4..937c4e4c6a 100644
--- a/indra/newview/app_settings/logcontrol.xml
+++ b/indra/newview/app_settings/logcontrol.xml
@@ -41,6 +41,8 @@
</array>
<key>tags</key>
<array>
+ <!-- sample entry for debugging a specific item -->
+<!-- <string>Voice</string> -->
</array>
</map>
</array>
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 53af87a870..864a79c38a 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10759,6 +10759,28 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>VoiceEffectExpiryWarningTime</key>
+ <map>
+ <key>Comment</key>
+ <string>How much notice to give of Voice Morph subscriptions expiry, in seconds.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>259200</integer>
+ </map>
+ <key>VoiceMorphingEnabled</key>
+ <map>
+ <key>Comment</key>
+ <string>Whether or not to enable Voice Morphs and show the UI.</string>
+ <key>Persist</key>
+ <integer>0</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
<key>AutoDisengageMic</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml
index 3ce32a05b0..d4000e9253 100644
--- a/indra/newview/app_settings/settings_per_account.xml
+++ b/indra/newview/app_settings/settings_per_account.xml
@@ -99,6 +99,17 @@
<key>Value</key>
<integer>1</integer>
</map>
+ <key>VoiceEffectDefault</key>
+ <map>
+ <key>Comment</key>
+ <string>Selected Voice Morph</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>00000000-0000-0000-0000-000000000000</string>
+ </map>
<!-- Settings below are for back compatibility only.
They are not used in current viewer anymore. But they can't be removed to avoid
diff --git a/indra/newview/llcallfloater.cpp b/indra/newview/llcallfloater.cpp
index dd99c6564c..60a2392d87 100644
--- a/indra/newview/llcallfloater.cpp
+++ b/indra/newview/llcallfloater.cpp
@@ -95,7 +95,7 @@ static void* create_non_avatar_caller(void*)
return new LLNonAvatarCaller;
}
-LLVoiceChannel* LLCallFloater::sCurrentVoiceCanel = NULL;
+LLVoiceChannel* LLCallFloater::sCurrentVoiceChannel = NULL;
LLCallFloater::LLCallFloater(const LLSD& key)
: LLTransientDockableFloater(NULL, false, key)
@@ -113,7 +113,7 @@ LLCallFloater::LLCallFloater(const LLSD& key)
mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLCallFloater::removeVoiceLeftParticipant, this, _1), voice_left_remove_delay);
mFactoryMap["non_avatar_caller"] = LLCallbackMap(create_non_avatar_caller, NULL);
- LLVoiceClient::getInstance()->addObserver(this);
+ LLVoiceClient::instance().addObserver(this);
LLTransientFloaterMgr::getInstance()->addControlView(this);
// force docked state since this floater doesn't save it between recreations
@@ -158,7 +158,6 @@ BOOL LLCallFloater::postBuild()
initAgentData();
-
connectToChannel(LLVoiceChannel::getCurrentVoiceChannel());
setIsChrome(true);
@@ -204,7 +203,7 @@ void LLCallFloater::draw()
}
// virtual
-void LLCallFloater::onChange()
+void LLCallFloater::onParticipantsChanged()
{
if (NULL == mParticipants) return;
updateParticipantsVoiceState();
@@ -287,22 +286,22 @@ void LLCallFloater::updateSession()
if (NULL == mSpeakerManager)
{
- // by default let show nearby chat participants
+ // By default show nearby chat participants
mSpeakerManager = LLLocalSpeakerMgr::getInstance();
LL_DEBUGS("Voice") << "Set DEFAULT speaker manager" << LL_ENDL;
mVoiceType = VC_LOCAL_CHAT;
}
updateTitle();
-
- //hide "Leave Call" button for nearby chat
+
+ // Hide "Leave Call" button for nearby chat
bool is_local_chat = mVoiceType == VC_LOCAL_CHAT;
childSetVisible("leave_call_btn_panel", !is_local_chat);
refreshParticipantList();
updateAgentModeratorState();
- //show floater for voice calls & only in CONNECTED to voice channel state
+ // Show floater for voice calls & only in CONNECTED to voice channel state
if (!is_local_chat &&
voice_channel &&
LLVoiceChannel::STATE_CONNECTED == voice_channel->getState())
@@ -368,7 +367,7 @@ void LLCallFloater::sOnCurrentChannelChanged(const LLUUID& /*session_id*/)
// *NOTE: if signal was sent for voice channel with LLVoiceChannel::STATE_NO_CHANNEL_INFO
// it sill be sent for the same channel again (when state is changed).
// So, lets ignore this call.
- if (channel == sCurrentVoiceCanel) return;
+ if (channel == sCurrentVoiceChannel) return;
LLCallFloater* call_floater = LLFloaterReg::getTypedInstance<LLCallFloater>("voice_controls");
@@ -715,9 +714,9 @@ void LLCallFloater::connectToChannel(LLVoiceChannel* channel)
{
mVoiceChannelStateChangeConnection.disconnect();
- sCurrentVoiceCanel = channel;
+ sCurrentVoiceChannel = channel;
- mVoiceChannelStateChangeConnection = sCurrentVoiceCanel->setStateChangedCallback(boost::bind(&LLCallFloater::onVoiceChannelStateChanged, this, _1, _2));
+ mVoiceChannelStateChangeConnection = sCurrentVoiceChannel->setStateChangedCallback(boost::bind(&LLCallFloater::onVoiceChannelStateChanged, this, _1, _2));
updateState(channel->getState());
}
@@ -737,7 +736,7 @@ void LLCallFloater::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old
void LLCallFloater::updateState(const LLVoiceChannel::EState& new_state)
{
- LL_DEBUGS("Voice") << "Updating state: " << new_state << ", session name: " << sCurrentVoiceCanel->getSessionName() << LL_ENDL;
+ LL_DEBUGS("Voice") << "Updating state: " << new_state << ", session name: " << sCurrentVoiceChannel->getSessionName() << LL_ENDL;
if (LLVoiceChannel::STATE_CONNECTED == new_state)
{
updateSession();
diff --git a/indra/newview/llcallfloater.h b/indra/newview/llcallfloater.h
index 0a8ea7de39..e4341175e2 100644
--- a/indra/newview/llcallfloater.h
+++ b/indra/newview/llcallfloater.h
@@ -47,15 +47,15 @@ class LLSpeakerMgr;
class LLSpeakersDelayActionsStorage;
/**
- * The Voice Control Panel is an ambient window summoned by clicking the flyout chevron on the Speak button.
- * It can be torn-off and freely positioned onscreen.
+ * The Voice Control Panel is an ambient window summoned by clicking the flyout chevron
+ * on the Speak button. It can be torn-off and freely positioned onscreen.
*
- * When the Resident is engaged in Nearby Voice Chat, the Voice Control Panel provides control over
- * the Resident's own microphone input volume, the audible volume of each of the other participants,
- * the Resident's own Voice Morphing settings (if she has subscribed to enable the feature), and Voice Recording.
+ * When the Resident is engaged in Voice Chat, the Voice Control Panel provides control
+ * over the audible volume of each of the other participants, the Resident's own Voice
+ * Morphing settings (if she has subscribed to enable the feature), and Voice Recording.
*
- * When the Resident is engaged in any chat except Nearby Chat, the Voice Control Panel also provides an
- * 'Leave Call' button to allow the Resident to leave that voice channel.
+ * When the Resident is engaged in any chat except Nearby Chat, the Voice Control Panel
+ * also provides a 'Leave Call' button to allow the Resident to leave that voice channel.
*/
class LLCallFloater : public LLTransientDockableFloater, LLVoiceClientParticipantObserver
{
@@ -75,7 +75,7 @@ public:
*
* Refreshes list to display participants not in voice as disabled.
*/
- /*virtual*/ void onChange();
+ /*virtual*/ void onParticipantsChanged();
static void sOnCurrentChannelChanged(const LLUUID& session_id);
@@ -259,7 +259,7 @@ private:
*
* @see sOnCurrentChannelChanged()
*/
- static LLVoiceChannel* sCurrentVoiceCanel;
+ static LLVoiceChannel* sCurrentVoiceChannel;
/* virtual */
LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; }
diff --git a/indra/newview/llfloatervoiceeffect.cpp b/indra/newview/llfloatervoiceeffect.cpp
new file mode 100644
index 0000000000..ca1f142760
--- /dev/null
+++ b/indra/newview/llfloatervoiceeffect.cpp
@@ -0,0 +1,288 @@
+/**
+ * @file llfloatervoiceeffect.cpp
+ * @author Aimee
+ * @brief Selection and preview of voice effect.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2002-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 "llfloatervoiceeffect.h"
+
+#include "llscrolllistctrl.h"
+#include "lltrans.h"
+#include "llweb.h"
+
+LLFloaterVoiceEffect::LLFloaterVoiceEffect(const LLSD& key)
+ : LLFloater(key)
+{
+ mCommitCallbackRegistrar.add("VoiceEffect.Record", boost::bind(&LLFloaterVoiceEffect::onClickRecord, this));
+ mCommitCallbackRegistrar.add("VoiceEffect.Play", boost::bind(&LLFloaterVoiceEffect::onClickPlay, this));
+ mCommitCallbackRegistrar.add("VoiceEffect.Stop", boost::bind(&LLFloaterVoiceEffect::onClickStop, this));
+// mCommitCallbackRegistrar.add("VoiceEffect.Activate", boost::bind(&LLFloaterVoiceEffect::onClickActivate, this));
+}
+
+// virtual
+LLFloaterVoiceEffect::~LLFloaterVoiceEffect()
+{
+ if(LLVoiceClient::instanceExists())
+ {
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->removeObserver(this);
+ }
+ }
+}
+
+// virtual
+BOOL LLFloaterVoiceEffect::postBuild()
+{
+ setDefaultBtn("record_btn");
+ getChild<LLButton>("record_btn")->setFocus(true);
+ childSetTextArg("voice_morphing_link", "[URL]", LLTrans::getString("voice_morphing_url"));
+
+ mVoiceEffectList = getChild<LLScrollListCtrl>("voice_effect_list");
+ if (mVoiceEffectList)
+ {
+ mVoiceEffectList->setCommitCallback(boost::bind(&LLFloaterVoiceEffect::onClickPlay, this));
+// mVoiceEffectList->setDoubleClickCallback(boost::bind(&LLFloaterVoiceEffect::onClickActivate, this));
+ }
+
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->addObserver(this);
+
+ // Disconnect from the current voice channel ready to record a voice sample for previewing
+ effect_interface->enablePreviewBuffer(true);
+ }
+
+ refreshEffectList();
+ updateControls();
+
+ return TRUE;
+}
+
+// virtual
+void LLFloaterVoiceEffect::onClose(bool app_quitting)
+{
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->enablePreviewBuffer(false);
+ }
+}
+
+void LLFloaterVoiceEffect::refreshEffectList()
+{
+ if (!mVoiceEffectList)
+ {
+ return;
+ }
+
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (!effect_interface)
+ {
+ mVoiceEffectList->setEnabled(false);
+ return;
+ }
+
+ LL_DEBUGS("Voice")<< "Rebuilding Voice Morph list."<< LL_ENDL;
+
+ // Preserve selected items and scroll position
+ S32 scroll_pos = mVoiceEffectList->getScrollPos();
+ uuid_vec_t selected_items;
+ std::vector<LLScrollListItem*> items = mVoiceEffectList->getAllSelected();
+ for(std::vector<LLScrollListItem*>::const_iterator it = items.begin(); it != items.end(); it++)
+ {
+ selected_items.push_back((*it)->getUUID());
+ }
+
+ mVoiceEffectList->deleteAllItems();
+
+ {
+ // Add the "No Voice Morph" entry
+ LLSD element;
+
+ element["id"] = LLUUID::null;
+ element["columns"][NAME_COLUMN]["column"] = "name";
+ element["columns"][NAME_COLUMN]["value"] = getString("no_voice_effect");
+ element["columns"][NAME_COLUMN]["font"]["style"] = "BOLD";
+
+ LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM);
+ // *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :(
+ if(sl_item)
+ {
+ ((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(LLFontGL::BOLD);
+ }
+ }
+
+ // Add each Voice Morph template, if there are any (template list includes all usable effects)
+ const voice_effect_list_t& template_list = effect_interface->getVoiceEffectTemplateList();
+ if (!template_list.empty())
+ {
+ for (voice_effect_list_t::const_iterator it = template_list.begin(); it != template_list.end(); ++it)
+ {
+ const LLUUID& effect_id = it->second;
+ std::string effect_name = it->first;
+
+ LLSD effect_properties = effect_interface->getVoiceEffectProperties(effect_id);
+
+ // Tag the active effect.
+ if (effect_id == LLVoiceClient::instance().getVoiceEffectDefault())
+ {
+ effect_name += " " + getString("active_voice_effect");
+ }
+
+ // Tag available effects that are new this session
+ if (effect_properties["is_new"].asBoolean())
+ {
+ effect_name += " " + getString("new_voice_effect");
+ }
+
+ LLDate expiry_date = effect_properties["expiry_date"].asDate();
+ bool is_template_only = effect_properties["template_only"].asBoolean();
+
+ std::string font_style = "NORMAL";
+ if (!is_template_only)
+ {
+ font_style = "BOLD";
+ }
+
+ LLSD element;
+ element["id"] = effect_id;
+
+ element["columns"][NAME_COLUMN]["column"] = "name";
+ element["columns"][NAME_COLUMN]["value"] = effect_name;
+ element["columns"][NAME_COLUMN]["font"]["style"] = font_style;
+
+ element["columns"][1]["column"] = "expires";
+ if (!is_template_only)
+ {
+ element["columns"][DATE_COLUMN]["value"] = expiry_date;
+ element["columns"][DATE_COLUMN]["type"] = "date";
+ }
+ else {
+ element["columns"][DATE_COLUMN]["value"] = getString("unsubscribed_voice_effect");
+ }
+// element["columns"][DATE_COLUMN]["font"]["style"] = "NORMAL";
+
+ LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM);
+ // *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :(
+ if(sl_item)
+ {
+ LLFontGL::StyleFlags style = is_template_only ? LLFontGL::NORMAL : LLFontGL::BOLD;
+ dynamic_cast<LLScrollListText*>(sl_item->getColumn(0))->setFontStyle(style);
+ }
+ }
+ }
+
+ // Re-select items that were selected before, and restore the scroll position
+ for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++)
+ {
+ mVoiceEffectList->selectByID(*it);
+ }
+ mVoiceEffectList->setScrollPos(scroll_pos);
+ mVoiceEffectList->setEnabled(true);
+}
+
+void LLFloaterVoiceEffect::updateControls()
+{
+ bool recording = false;
+
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ recording = effect_interface->isPreviewRecording();
+ }
+
+ getChild<LLButton>("record_btn")->setVisible(!recording);
+ getChild<LLButton>("record_stop_btn")->setVisible(recording);
+}
+
+// virtual
+void LLFloaterVoiceEffect::onVoiceEffectChanged(bool effect_list_updated)
+{
+ if (effect_list_updated)
+ {
+ refreshEffectList();
+ }
+ updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickRecord()
+{
+ LL_DEBUGS("Voice") << "Record clicked" << LL_ENDL;
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->recordPreviewBuffer();
+ }
+ updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickPlay()
+{
+ LL_DEBUGS("Voice") << "Play clicked" << LL_ENDL;
+ if (!mVoiceEffectList)
+ {
+ return;
+ }
+
+ const LLUUID& effect_id = mVoiceEffectList->getCurrentID();
+
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->playPreviewBuffer(effect_id);
+ }
+ updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickStop()
+{
+ LL_DEBUGS("Voice") << "Stop clicked" << LL_ENDL;
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->stopPreviewBuffer();
+ }
+ updateControls();
+}
+
+//void LLFloaterVoiceEffect::onClickActivate()
+//{
+// LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+// if (effect_interface && mVoiceEffectList)
+// {
+// effect_interface->setVoiceEffect(mVoiceEffectList->getCurrentID());
+// }
+//}
+
diff --git a/indra/newview/llfloatervoiceeffect.h b/indra/newview/llfloatervoiceeffect.h
new file mode 100644
index 0000000000..fe207a05c3
--- /dev/null
+++ b/indra/newview/llfloatervoiceeffect.h
@@ -0,0 +1,78 @@
+/**
+ * @file llfloatervoiceeffect.h
+ * @author Aimee
+ * @brief Selection and preview of voice effects.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2002-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_LLFLOATERVOICEEFFECT_H
+#define LL_LLFLOATERVOICEEFFECT_H
+
+#include "llfloater.h"
+#include "llvoiceclient.h"
+
+class LLButton;
+class LLScrollListCtrl;
+
+class LLFloaterVoiceEffect
+ : public LLFloater
+ , public LLVoiceEffectObserver
+{
+public:
+ LOG_CLASS(LLFloaterVoiceEffect);
+
+ LLFloaterVoiceEffect(const LLSD& key);
+ virtual ~LLFloaterVoiceEffect();
+
+ virtual BOOL postBuild();
+ virtual void onClose(bool app_quitting);
+
+private:
+ enum ColumnIndex
+ {
+ NAME_COLUMN = 0,
+ DATE_COLUMN = 1,
+ };
+
+ void refreshEffectList();
+ void updateControls();
+
+ /// Called by voice effect provider when voice effect list is changed.
+ virtual void onVoiceEffectChanged(bool effect_list_updated);
+
+ void onClickRecord();
+ void onClickPlay();
+ void onClickStop();
+// void onClickActivate();
+
+ LLUUID mSelectedID;
+ LLScrollListCtrl* mVoiceEffectList;
+};
+
+#endif
diff --git a/indra/newview/llpanelvoiceeffect.cpp b/indra/newview/llpanelvoiceeffect.cpp
new file mode 100644
index 0000000000..fd470798ee
--- /dev/null
+++ b/indra/newview/llpanelvoiceeffect.cpp
@@ -0,0 +1,169 @@
+/**
+ * @file llpanelvoiceeffect.cpp
+ * @author Aimee
+ * @brief Panel to select Voice Morphs.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, 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 "llpanelvoiceeffect.h"
+
+#include "llcombobox.h"
+#include "llfloaterreg.h"
+#include "llpanel.h"
+#include "lltrans.h"
+#include "lltransientfloatermgr.h"
+#include "llvoiceclient.h"
+
+static LLRegisterPanelClassWrapper<LLPanelVoiceEffect> t_panel_voice_effect("panel_voice_effect");
+
+LLPanelVoiceEffect::LLPanelVoiceEffect()
+ : mVoiceEffectCombo(NULL)
+{
+ mCommitCallbackRegistrar.add("Voice.CommitVoiceEffect", boost::bind(&LLPanelVoiceEffect::onCommitVoiceEffect, this));
+}
+
+LLPanelVoiceEffect::~LLPanelVoiceEffect()
+{
+ LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox");
+ LLTransientFloaterMgr::getInstance()->removeControlView(combo_list_view);
+
+ if(LLVoiceClient::instanceExists())
+ {
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->removeObserver(this);
+ }
+ }
+}
+
+// virtual
+BOOL LLPanelVoiceEffect::postBuild()
+{
+ mVoiceEffectCombo = getChild<LLComboBox>("voice_effect");
+
+ // Need to tell LLTransientFloaterMgr about the combo list, otherwise it can't
+ // be clicked while in a docked floater as it extends outside the floater area.
+ LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox");
+ LLTransientFloaterMgr::getInstance()->addControlView(combo_list_view);
+
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interface)
+ {
+ effect_interface->addObserver(this);
+ }
+
+ update(true);
+
+ return TRUE;
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// PRIVATE SECTION
+//////////////////////////////////////////////////////////////////////////
+
+void LLPanelVoiceEffect::onCommitVoiceEffect()
+{
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (!effect_interface)
+ {
+ mVoiceEffectCombo->setEnabled(false);
+ return;
+ }
+
+ LLSD value = mVoiceEffectCombo->getValue();
+ if (value.asInteger() == PREVIEW_VOICE_EFFECTS)
+ {
+ // Open the Voice Morph preview floater
+ LLFloaterReg::showInstance("voice_effect");
+ }
+ else if (value.asInteger() == GET_VOICE_EFFECTS)
+ {
+ // Open the voice morphing info web page
+ LLWeb::loadURL(LLTrans::getString("voice_morphing_url"));
+ }
+ else
+ {
+ effect_interface->setVoiceEffect(value.asUUID());
+ }
+
+ mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect());
+}
+
+// virtual
+void LLPanelVoiceEffect::onVoiceEffectChanged(bool effect_list_updated)
+{
+ update(effect_list_updated);
+}
+
+void LLPanelVoiceEffect::update(bool list_updated)
+{
+ if (mVoiceEffectCombo)
+ {
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (list_updated)
+ {
+ // Add the default "No Voice Morph" entry.
+ mVoiceEffectCombo->removeall();
+ mVoiceEffectCombo->add(getString("no_voice_effect"), LLUUID::null);
+ mVoiceEffectCombo->addSeparator();
+
+ // Add entries for each Voice Morph.
+ const voice_effect_list_t& effect_list = effect_interface->getVoiceEffectList();
+ if (!effect_list.empty())
+ {
+ for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it)
+ {
+ mVoiceEffectCombo->add(it->first, it->second, ADD_BOTTOM);
+ }
+
+ mVoiceEffectCombo->addSeparator();
+ }
+
+ // Add the fixed entries to go to the preview floater or marketing page.
+ mVoiceEffectCombo->add(getString("preview_voice_effects"), PREVIEW_VOICE_EFFECTS);
+ mVoiceEffectCombo->add(getString("get_voice_effects"), GET_VOICE_EFFECTS);
+ }
+
+ if (effect_interface && LLVoiceClient::instance().isVoiceWorking())
+ {
+ // Select the current Voice Morph.
+ mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect());
+ mVoiceEffectCombo->setEnabled(true);
+ }
+ else
+ {
+ // If voice isn't working or Voice Effects are not supported disable the control.
+ mVoiceEffectCombo->setValue(LLUUID::null);
+ mVoiceEffectCombo->setEnabled(false);
+ }
+ }
+}
diff --git a/indra/newview/llpanelvoiceeffect.h b/indra/newview/llpanelvoiceeffect.h
new file mode 100644
index 0000000000..b5bf2f05a8
--- /dev/null
+++ b/indra/newview/llpanelvoiceeffect.h
@@ -0,0 +1,73 @@
+/**
+ * @file llpanelvoiceeffect.h
+ * @author Aimee
+ * @brief Panel to select Voice Effects.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, 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_PANELVOICEEFFECT_H
+#define LL_PANELVOICEEFFECT_H
+
+#include "llpanel.h"
+#include "llvoiceclient.h"
+
+class LLComboBox;
+
+class LLPanelVoiceEffect
+ : public LLPanel
+ , public LLVoiceEffectObserver
+{
+public:
+ LOG_CLASS(LLPanelVoiceEffect);
+
+ LLPanelVoiceEffect();
+ virtual ~LLPanelVoiceEffect();
+
+ virtual BOOL postBuild();
+
+private:
+ void onCommitVoiceEffect();
+ void update(bool list_updated);
+
+ /// Called by voice effect provider when voice effect list is changed.
+ virtual void onVoiceEffectChanged(bool effect_list_updated);
+
+ // Fixed entries in the Voice Morph list
+ typedef enum e_voice_effect_combo_items
+ {
+ NO_VOICE_EFFECT = 0,
+ PREVIEW_VOICE_EFFECTS = 1,
+ GET_VOICE_EFFECTS = 2
+ } EVoiceEffectComboItems;
+
+ LLComboBox* mVoiceEffectCombo;
+};
+
+
+#endif //LL_PANELVOICEEFFECT_H
diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp
index f020ad9bc2..a27afeab7c 100644
--- a/indra/newview/llparticipantlist.cpp
+++ b/indra/newview/llparticipantlist.cpp
@@ -94,7 +94,7 @@ public:
mAvalineCallers.insert(avaline_caller_id);
}
- void onChange()
+ void onParticipantsChanged()
{
uuid_set_t participant_uuids;
LLVoiceClient::getInstance()->getParticipantList(participant_uuids);
diff --git a/indra/newview/llspeakingindicatormanager.cpp b/indra/newview/llspeakingindicatormanager.cpp
index 29237946d2..ea7601517d 100644
--- a/indra/newview/llspeakingindicatormanager.cpp
+++ b/indra/newview/llspeakingindicatormanager.cpp
@@ -107,7 +107,7 @@ private:
* So, method does not calculate difference between these list it only switches off already
* switched on indicators and switches on indicators of voice channel participants
*/
- void onChange();
+ void onParticipantsChanged();
/**
* Changes state of indicators specified by LLUUIDs
@@ -205,7 +205,7 @@ void SpeakingIndicatorManager::sOnCurrentChannelChanged(const LLUUID& /*session_
mSwitchedIndicatorsOn.clear();
}
-void SpeakingIndicatorManager::onChange()
+void SpeakingIndicatorManager::onParticipantsChanged()
{
LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL;
diff --git a/indra/newview/lltexlayer.cpp b/indra/newview/lltexlayer.cpp
index d001d692a3..5d51e32515 100644
--- a/indra/newview/lltexlayer.cpp
+++ b/indra/newview/lltexlayer.cpp
@@ -378,40 +378,33 @@ BOOL LLTexLayerSetBuffer::updateImmediate()
void LLTexLayerSetBuffer::readBackAndUpload()
{
- // pointers for storing data to upload
- U8* baked_color_data = new U8[ mFullWidth * mFullHeight * 4 ];
-
- glReadPixels(mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, GL_RGBA, GL_UNSIGNED_BYTE, baked_color_data );
- stop_glerror();
-
- llinfos << "Baked " << mTexLayerSet->getBodyRegionName() << llendl;
+ llinfos << "Uploading baked " << mTexLayerSet->getBodyRegionName() << llendl;
LLViewerStats::getInstance()->incStat(LLViewerStats::ST_TEX_BAKES);
- // We won't need our caches since we're baked now. (Techically, we won't
- // really be baked until this image is sent to the server and the Avatar
- // Appearance message is received.)
+ // Don't need caches since we're baked now. (note: we won't *really* be baked
+ // until this image is sent to the server and the Avatar Appearance message is received.)
mTexLayerSet->deleteCaches();
- LLGLSUIDefault gls_ui;
+ // Get the COLOR information from our texture
+ U8* baked_color_data = new U8[ mFullWidth * mFullHeight * 4 ];
+ glReadPixels(mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, GL_RGBA, GL_UNSIGNED_BYTE, baked_color_data );
+ stop_glerror();
+ // Get the MASK information from our texture
+ LLGLSUIDefault gls_ui;
LLPointer<LLImageRaw> baked_mask_image = new LLImageRaw(mFullWidth, mFullHeight, 1 );
U8* baked_mask_data = baked_mask_image->getData();
-
mTexLayerSet->gatherMorphMaskAlpha(baked_mask_data, mFullWidth, mFullHeight);
- // writes into baked_color_data
- const char* comment_text = NULL;
- S32 baked_image_components = 5; // red green blue [bump] clothing
+ // Create the baked image from our color and mask information
+ const S32 baked_image_components = 5; // red green blue [bump] clothing
LLPointer<LLImageRaw> baked_image = new LLImageRaw( mFullWidth, mFullHeight, baked_image_components );
U8* baked_image_data = baked_image->getData();
-
- comment_text = LINDEN_J2C_COMMENT_PREFIX "RGBHM"; // 5 channels: rgb, heightfield/alpha, mask
-
S32 i = 0;
- for( S32 u = 0; u < mFullWidth; u++ )
+ for (S32 u=0; u < mFullWidth; u++)
{
- for( S32 v = 0; v < mFullHeight; v++ )
+ for (S32 v=0; v < mFullHeight; v++)
{
baked_image_data[5*i + 0] = baked_color_data[4*i + 0];
baked_image_data[5*i + 1] = baked_color_data[4*i + 1];
@@ -424,21 +417,19 @@ void LLTexLayerSetBuffer::readBackAndUpload()
LLPointer<LLImageJ2C> compressedImage = new LLImageJ2C;
compressedImage->setRate(0.f);
- LLTransactionID tid;
- LLAssetID asset_id;
- tid.generate();
- asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
-
- BOOL res = false;
- if( compressedImage->encode(baked_image, comment_text))
+ const char* comment_text = LINDEN_J2C_COMMENT_PREFIX "RGBHM"; // writes into baked_color_data. 5 channels (rgb, heightfield/alpha, mask)
+ if (compressedImage->encode(baked_image, comment_text))
{
- res = LLVFile::writeFile(compressedImage->getData(), compressedImage->getDataSize(),
- gVFS, asset_id, LLAssetType::AT_TEXTURE);
- if (res)
+ LLTransactionID tid;
+ tid.generate();
+ const LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
+ if (LLVFile::writeFile(compressedImage->getData(), compressedImage->getDataSize(),
+ gVFS, asset_id, LLAssetType::AT_TEXTURE))
{
- LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C;
+ // Read back the file and validate.
BOOL valid = FALSE;
- S32 file_size;
+ LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C;
+ S32 file_size = 0;
U8* data = LLVFile::readFile(gVFS, asset_id, LLAssetType::AT_TEXTURE, &file_size);
if (data)
{
@@ -449,31 +440,26 @@ void LLTexLayerSetBuffer::readBackAndUpload()
integrity_test->setLastError("Unable to read entire file");
}
- if( valid )
+ if (valid)
{
- // baked_upload_data is owned by the responder and deleted after the request completes
+ // Baked_upload_data is owned by the responder and deleted after the request completes.
LLBakedUploadData* baked_upload_data = new LLBakedUploadData(gAgentAvatarp,
this->mTexLayerSet,
asset_id);
mUploadID = asset_id;
- const BOOL highest_lod = mTexLayerSet->isLocalTextureDataFinal();
-
- // upload the image
- std::string url = gAgent.getRegion()->getCapability("UploadBakedTexture");
+ // Upload the image
+ const std::string url = gAgent.getRegion()->getCapability("UploadBakedTexture");
if(!url.empty()
- && !LLPipeline::sForceOldBakedUpload) // Toggle the debug setting UploadBakedTexOld to change between the new caps method and old method
+ && !LLPipeline::sForceOldBakedUpload) // toggle debug setting UploadBakedTexOld to change between the new caps method and old method
{
- llinfos << "Baked texture upload via capability of " << mUploadID << " to " << url << llendl;
-
LLSD body = LLSD::emptyMap();
+ // The responder will call LLTexLayerSetBuffer::onTextureUploadComplete()
LLHTTPClient::post(url, body, new LLSendTexLayerResponder(body, mUploadID, LLAssetType::AT_TEXTURE, baked_upload_data));
- // Responder will call LLTexLayerSetBuffer::onTextureUploadComplete()
+ llinfos << "Baked texture upload via capability of " << mUploadID << " to " << url << llendl;
}
else
{
- llinfos << "Baked texture upload via Asset Store." << llendl;
- // gAssetStorage->storeAssetData(mTransactionID, LLAssetType::AT_IMAGE_JPEG, &uploadCallback, (void *)this, FALSE);
gAssetStorage->storeAssetData(tid,
LLAssetType::AT_TEXTURE,
LLTexLayerSetBuffer::onTextureUploadComplete,
@@ -481,49 +467,53 @@ void LLTexLayerSetBuffer::readBackAndUpload()
TRUE, // temp_file
TRUE, // is_priority
TRUE); // store_local
+ llinfos << "Baked texture upload via Asset Store." << llendl;
}
- if (gSavedSettings.getBOOL("DebugAvatarRezTime"))
- {
- std::string lod_str = highest_lod ? "HighRes" : "LowRes";
- LLSD args;
- args["EXISTENCE"] = llformat("%d",(U32)mTexLayerSet->getAvatar()->debugGetExistenceTimeElapsedF32());
- args["TIME"] = llformat("%d",(U32)mNeedsUploadTimer.getElapsedTimeF32());
- args["BODYREGION"] = mTexLayerSet->getBodyRegionName();
- args["RESOLUTION"] = lod_str;
- LLNotificationsUtil::add("AvatarRezSelfBakeNotification",args);
- llinfos << "Uploading [ name: " << mTexLayerSet->getBodyRegionName() << " res:" << lod_str << " time:" << (U32)mNeedsUploadTimer.getElapsedTimeF32() << " ]" << llendl;
- }
-
+ const BOOL highest_lod = mTexLayerSet->isLocalTextureDataFinal();
if (highest_lod)
{
- // Sending the final LOD for the baked texture.
- // All done, pause the upload timer so we know how long it took.
+ // Sending the final LOD for the baked texture. All done, pause
+ // the upload timer so we know how long it took.
mNeedsUpload = FALSE;
mNeedsUploadTimer.pause();
}
else
{
- // Sending a lower level LOD for the baked texture.
- // Restart the upload timer.
+ // Sending a lower level LOD for the baked texture. Restart the upload timer.
mNumLowresUploads++;
mNeedsUploadTimer.unpause();
mNeedsUploadTimer.reset();
}
+
+ // Print out notification that we uploaded this texture.
+ if (gSavedSettings.getBOOL("DebugAvatarRezTime"))
+ {
+ std::string lod_str = highest_lod ? "HighRes" : "LowRes";
+ LLSD args;
+ args["EXISTENCE"] = llformat("%d",(U32)mTexLayerSet->getAvatar()->debugGetExistenceTimeElapsedF32());
+ args["TIME"] = llformat("%d",(U32)mNeedsUploadTimer.getElapsedTimeF32());
+ args["BODYREGION"] = mTexLayerSet->getBodyRegionName();
+ args["RESOLUTION"] = lod_str;
+ LLNotificationsUtil::add("AvatarRezSelfBakeNotification",args);
+ llinfos << "Uploading [ name: " << mTexLayerSet->getBodyRegionName() << " res:" << lod_str << " time:" << (U32)mNeedsUploadTimer.getElapsedTimeF32() << " ]" << llendl;
+ }
}
else
{
+ // The read back and validate operation failed. Remove the uploaded file.
mUploadPending = FALSE;
- llinfos << "unable to create baked upload file: corrupted" << llendl;
LLVFile file(gVFS, asset_id, LLAssetType::AT_TEXTURE, LLVFile::WRITE);
file.remove();
+ llinfos << "Unable to create baked upload file (reason: corrupted)." << llendl;
}
}
}
- if (!res)
+ else
{
+ // The VFS write file operation failed.
mUploadPending = FALSE;
- llinfos << "unable to create baked upload file" << llendl;
+ llinfos << "Unable to create baked upload file (reason: failed to write file)" << llendl;
}
delete [] baked_color_data;
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 74b7f123d8..52d227f827 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -329,11 +329,7 @@ public:
partial = true;
}
}
- else
- {
- worker->setGetStatus(status, reason);
-// llwarns << status << ": " << reason << llendl;
- }
+
if (!success)
{
worker->setGetStatus(status, reason);
@@ -912,7 +908,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
if (mGetStatus == HTTP_NOT_FOUND)
{
mHTTPFailCount = max_attempts = 1; // Don't retry
- //llwarns << "Texture missing from server (404): " << mUrl << llendl;
+ llwarns << "Texture missing from server (404): " << mUrl << llendl;
//roll back to try UDP
if(mCanUseNET)
@@ -932,6 +928,17 @@ bool LLTextureFetchWorker::doWork(S32 param)
max_attempts = mHTTPFailCount+1; // Keep retrying
LL_INFOS_ONCE("Texture") << "Texture server busy (503): " << mUrl << LL_ENDL;
}
+ else if(mGetStatus >= HTTP_MULTIPLE_CHOICES && mGetStatus < HTTP_BAD_REQUEST) //http re-direct
+ {
+ ++mHTTPFailCount;
+ max_attempts = 5 ; //try at most 5 times to avoid infinite redirection loop.
+
+ llwarns << "HTTP GET failed because of redirection: " << mUrl
+ << " Status: " << mGetStatus << " Reason: '" << mGetReason << llendl ;
+
+ //assign to the new url
+ mUrl = mGetReason ;
+ }
else
{
const S32 HTTP_MAX_RETRY_COUNT = 3;
@@ -941,6 +948,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
<< " Status: " << mGetStatus << " Reason: '" << mGetReason << "'"
<< " Attempt:" << mHTTPFailCount+1 << "/" << max_attempts << llendl;
}
+
if (mHTTPFailCount >= max_attempts)
{
if (cur_size > 0)
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 49ea0348f9..efe59744bc 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -103,6 +103,7 @@
#include "llfloateruipreview.h"
#include "llfloaterurldisplay.h"
#include "llfloatervoicedevicesettings.h"
+#include "llfloatervoiceeffect.h"
#include "llfloaterwater.h"
#include "llfloaterwhitelistentry.h"
#include "llfloaterwindlight.h"
@@ -255,7 +256,8 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("upload_sound", "floater_sound_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSoundPreview>, "upload");
LLFloaterReg::add("voice_controls", "floater_voice_controls.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLCallFloater>);
-
+ LLFloaterReg::add("voice_effect", "floater_voice_effect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterVoiceEffect>);
+
LLFloaterReg::add("whitelist_entry", "floater_whitelist_entry.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWhiteListEntry>);
LLFloaterWindowSizeUtil::registerFloater();
LLFloaterReg::add("world_map", "floater_world_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWorldMap>);
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index 1b4471a9fe..070663e22f 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -897,9 +897,9 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s
else
{
LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL;
- // In case of incoming AvaLine call generated URI will be differ from original one.
- // This is because Avatar-2-Avatar URI is based on avatar UUID but Avaline is not.
- // See LLVoiceClient::sessionAddedEvent() -> setUUIDFromStringHash()
+ // In the case of an incoming AvaLine call, the generated URI will be different from the
+ // original one. This is because the P2P URI is based on avatar UUID but Avaline is not.
+ // See LLVoiceClient::sessionAddedEvent()
setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
}
diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h
index 573fab1f4f..074f9b8bba 100644
--- a/indra/newview/llvoicechannel.h
+++ b/indra/newview/llvoicechannel.h
@@ -113,7 +113,7 @@ protected:
void doSetState(const EState& state);
void setURI(std::string uri);
- // there can be two directions ICOMING and OUTGOING
+ // there can be two directions INCOMING and OUTGOING
EDirection mCallDirection;
std::string mURI;
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index 42e44634b6..e8635d7f1a 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -35,6 +35,7 @@
#include "llviewerwindow.h"
#include "llvoicevivox.h"
#include "llviewernetwork.h"
+#include "llcommandhandler.h"
#include "llhttpnode.h"
#include "llnotificationsutil.h"
#include "llsdserialize.h"
@@ -46,6 +47,39 @@ const F32 LLVoiceClient::VOLUME_MIN = 0.f;
const F32 LLVoiceClient::VOLUME_DEFAULT = 0.5f;
const F32 LLVoiceClient::VOLUME_MAX = 1.0f;
+
+// Support for secondlife:///app/voice SLapps
+class LLVoiceHandler : public LLCommandHandler
+{
+public:
+ // requests will be throttled from a non-trusted browser
+ LLVoiceHandler() : LLCommandHandler("voice", UNTRUSTED_THROTTLE) {}
+
+ bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
+ {
+ if (params[0].asString() == "effects")
+ {
+ LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+ // If the voice client doesn't support voice effects, we can't handle effects SLapps
+ if (!effect_interface)
+ {
+ return false;
+ }
+
+ // Support secondlife:///app/voice/effects/refresh to update the voice effect list with new effects
+ if (params[1].asString() == "refresh")
+ {
+ effect_interface->refreshVoiceEffectLists(false);
+ return true;
+ }
+ }
+ return false;
+ }
+};
+LLVoiceHandler gVoiceHandler;
+
+
+
std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
{
std::string result = "UNKNOWN";
@@ -77,13 +111,14 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv
-
///////////////////////////////////////////////////////////////////////////////////////////////
LLVoiceClient::LLVoiceClient()
:
mVoiceModule(NULL),
- m_servicePump(NULL)
+ m_servicePump(NULL),
+ mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled")),
+ mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault"))
{
}
@@ -567,7 +602,7 @@ std::string LLVoiceClient::getDisplayName(const LLUUID& id)
}
}
-bool LLVoiceClient::isVoiceWorking()
+bool LLVoiceClient::isVoiceWorking() const
{
if (mVoiceModule)
{
@@ -710,6 +745,10 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
}
}
+LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const
+{
+ return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL;
+}
///////////////////
// version checking
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index e08fed7ae9..0e3d9a5435 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -42,6 +42,7 @@ class LLVOAvatar;
#include "llviewerregion.h"
#include "llcallingcard.h" // for LLFriendObserver
#include "llsecapi.h"
+#include "llcontrol.h"
// devices
@@ -52,7 +53,7 @@ class LLVoiceClientParticipantObserver
{
public:
virtual ~LLVoiceClientParticipantObserver() { }
- virtual void onChange() = 0;
+ virtual void onParticipantsChanged() = 0;
};
@@ -109,7 +110,7 @@ public:
virtual void updateSettings()=0; // call after loading settings and whenever they change
- virtual bool isVoiceWorking()=0; // connected to a voice server and voice channel
+ virtual bool isVoiceWorking() const = 0; // connected to a voice server and voice channel
virtual const LLVoiceVersionInfo& getVersion()=0;
@@ -217,8 +218,6 @@ public:
//////////////////////////
/// @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;
@@ -261,6 +260,63 @@ public:
};
+//////////////////////////////////
+/// @class LLVoiceEffectObserver
+class LLVoiceEffectObserver
+{
+public:
+ virtual ~LLVoiceEffectObserver() { }
+ virtual void onVoiceEffectChanged(bool effect_list_updated) = 0;
+};
+
+typedef std::multimap<const std::string, const LLUUID, LLDictionaryLess> voice_effect_list_t;
+
+//////////////////////////////////
+/// @class LLVoiceEffectInterface
+/// @brief Voice effect module interface
+///
+/// Voice effect modules should provide an implementation for this interface.
+/////////////////////////////////
+
+class LLVoiceEffectInterface
+{
+public:
+ LLVoiceEffectInterface() {}
+ virtual ~LLVoiceEffectInterface() {}
+
+ //////////////////////////
+ /// @name Accessors
+ //@{
+ virtual bool setVoiceEffect(const LLUUID& id) = 0;
+ virtual const LLUUID getVoiceEffect() = 0;
+ virtual LLSD getVoiceEffectProperties(const LLUUID& id) = 0;
+
+ virtual void refreshVoiceEffectLists(bool clear_lists) = 0;
+ virtual const voice_effect_list_t &getVoiceEffectList() const = 0;
+ virtual const voice_effect_list_t &getVoiceEffectTemplateList() const = 0;
+ //@}
+
+ //////////////////////////////
+ /// @name Status notification
+ //@{
+ virtual void addObserver(LLVoiceEffectObserver* observer) = 0;
+ virtual void removeObserver(LLVoiceEffectObserver* observer) = 0;
+ //@}
+
+ //////////////////////////////
+ /// @name Preview buffer
+ //@{
+ virtual void enablePreviewBuffer(bool enable) = 0;
+ virtual void recordPreviewBuffer() = 0;
+ virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) = 0;
+ virtual void stopPreviewBuffer() = 0;
+
+ virtual bool isPreviewRecording() = 0;
+ virtual bool isPreviewPlaying() = 0;
+ //@}
+};
+
+
class LLVoiceClient: public LLSingleton<LLVoiceClient>
{
LOG_CLASS(LLVoiceClient);
@@ -281,7 +337,7 @@ public:
void updateSettings(); // call after loading settings and whenever they change
- bool isVoiceWorking(); // connected to a voice server and voice channel
+ bool isVoiceWorking() const; // connected to a voice server and voice channel
// tuning
void tuningStart();
@@ -403,10 +459,23 @@ public:
void removeObserver(LLVoiceClientParticipantObserver* observer);
std::string sipURIFromID(const LLUUID &id);
-
+
+ //////////////////////////
+ /// @name Voice effects
+ //@{
+ bool getVoiceEffectEnabled() const { return mVoiceEffectEnabled; };
+ LLUUID getVoiceEffectDefault() const { return LLUUID(mVoiceEffectDefault); };
+
+ // Returns NULL if voice effects are not supported, or not enabled.
+ LLVoiceEffectInterface* getVoiceEffectInterface() const;
+ //@}
+
protected:
LLVoiceModuleInterface* mVoiceModule;
LLPumpIO *m_servicePump;
+
+ LLCachedControl<bool> mVoiceEffectEnabled;
+ LLCachedControl<std::string> mVoiceEffectDefault;
};
/**
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index c6c155f0f0..96bde129ee 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -60,6 +60,7 @@
#include "llviewerparcelmgr.h"
//#include "llfirstuse.h"
#include "llspeakers.h"
+#include "lltrans.h"
#include "llviewerwindow.h"
#include "llviewercamera.h"
@@ -67,15 +68,11 @@
#include "llviewernetwork.h"
#include "llnotificationsutil.h"
+#include "stringize.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 VOLUME_SCALE_VIVOX = 0.01f;
@@ -100,14 +97,15 @@ const int MAX_LOGIN_RETRIES = 12;
// blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability.
const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50;
+// How often to check for expired voice fonts in seconds
+const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f;
+// Time of day at which Vivox expires voice font subscriptions.
+// Used to replace the time portion of received expiry timestamps.
+static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z";
+
+// Maximum length of capture buffer recordings in seconds.
+const F32 CAPTURE_BUFFER_MAX_TIME = 10.f;
-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)
{
@@ -325,6 +323,7 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
mBuddyListMapPopulated(false),
mBlockRulesListReceived(false),
mAutoAcceptRulesListReceived(false),
+
mCaptureDeviceDirty(false),
mRenderDeviceDirty(false),
mSpatialCoordsDirty(false),
@@ -348,10 +347,17 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
mVoiceEnabled(false),
mWriteInProgress(false),
- mLipSyncEnabled(false)
-
+ mLipSyncEnabled(false),
+ mVoiceFontsReceived(false),
+ mVoiceFontsNew(false),
+ mVoiceFontListDirty(false),
+ mCaptureBufferMode(false),
+ mCaptureBufferRecording(false),
+ mCaptureBufferRecorded(false),
+ mCaptureBufferPlaying(false),
+ mPlayRequestCount(0)
{
mSpeakerVolume = scale_speaker_volume(0);
@@ -654,6 +660,11 @@ std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState)
CASE(stateMicTuningStart);
CASE(stateMicTuningRunning);
CASE(stateMicTuningStop);
+ CASE(stateCaptureBufferPaused);
+ CASE(stateCaptureBufferRecStart);
+ CASE(stateCaptureBufferRecording);
+ CASE(stateCaptureBufferPlayStart);
+ CASE(stateCaptureBufferPlaying);
CASE(stateConnectorStart);
CASE(stateConnectorStarting);
CASE(stateConnectorStarted);
@@ -662,6 +673,8 @@ std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState)
CASE(stateNeedsLogin);
CASE(stateLoggingIn);
CASE(stateLoggedIn);
+ CASE(stateVoiceFontsWait);
+ CASE(stateVoiceFontsReceived);
CASE(stateCreatingSessionGroup);
CASE(stateNoChannel);
CASE(stateJoiningSession);
@@ -775,8 +788,10 @@ void LLVivoxVoiceClient::stateMachine()
// Clean up and reset everything.
closeSocket();
deleteAllSessions();
- deleteAllBuddies();
-
+ deleteAllBuddies();
+ deleteAllVoiceFonts();
+ deleteVoiceFontTemplates();
+
mConnectorHandle.clear();
mAccountHandle.clear();
mAccountPassword.clear();
@@ -1126,8 +1141,97 @@ void LLVivoxVoiceClient::stateMachine()
}
break;
-
- //MARK: stateConnectorStart
+
+ //MARK: stateCaptureBufferPaused
+ case stateCaptureBufferPaused:
+ if (!mCaptureBufferMode)
+ {
+ // Leaving capture mode.
+
+ mCaptureBufferRecording = false;
+ mCaptureBufferRecorded = false;
+ mCaptureBufferPlaying = false;
+
+ // Return to stateNoChannel to trigger reconnection to a channel.
+ setState(stateNoChannel);
+ }
+ else if (mCaptureBufferRecording)
+ {
+ setState(stateCaptureBufferRecStart);
+ }
+ else if (mCaptureBufferPlaying)
+ {
+ setState(stateCaptureBufferPlayStart);
+ }
+ break;
+
+ //MARK: stateCaptureBufferRecStart
+ case stateCaptureBufferRecStart:
+ captureBufferRecordStartSendMessage();
+
+ // Flag that something is recorded to allow playback.
+ mCaptureBufferRecorded = true;
+
+ // Start the timer, recording will be stopped when it expires.
+ mCaptureTimer.start();
+ mCaptureTimer.setTimerExpirySec(CAPTURE_BUFFER_MAX_TIME);
+
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ setState(stateCaptureBufferRecording);
+ break;
+
+ //MARK: stateCaptureBufferRecording
+ case stateCaptureBufferRecording:
+ if (!mCaptureBufferMode || !mCaptureBufferRecording ||
+ mCaptureBufferPlaying || mCaptureTimer.hasExpired())
+ {
+ // Stop recording
+ captureBufferRecordStopSendMessage();
+ mCaptureBufferRecording = false;
+
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ setState(stateCaptureBufferPaused);
+ }
+ break;
+
+ //MARK: stateCaptureBufferPlayStart
+ case stateCaptureBufferPlayStart:
+ captureBufferPlayStartSendMessage(mPreviewVoiceFont);
+
+ // Store the voice font being previewed, so that we know to restart if it changes.
+ mPreviewVoiceFontLast = mPreviewVoiceFont;
+
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ setState(stateCaptureBufferPlaying);
+ break;
+
+ //MARK: stateCaptureBufferPlaying
+ case stateCaptureBufferPlaying:
+ if (mCaptureBufferPlaying && mPreviewVoiceFont != mPreviewVoiceFontLast)
+ {
+ // If the preview voice font changes, restart playing with the new font.
+ setState(stateCaptureBufferPlayStart);
+ }
+ else if (!mCaptureBufferMode || !mCaptureBufferPlaying || mCaptureBufferRecording)
+ {
+ // Stop playing.
+ captureBufferPlayStopSendMessage();
+ mCaptureBufferPlaying = false;
+
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ setState(stateCaptureBufferPaused);
+ }
+ break;
+
+ //MARK: stateConnectorStart
case stateConnectorStart:
if(!mVoiceEnabled)
{
@@ -1222,6 +1326,18 @@ void LLVivoxVoiceClient::stateMachine()
notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
+ if (LLVoiceClient::instance().getVoiceEffectEnabled())
+ {
+ // Request the set of available voice fonts.
+ setState(stateVoiceFontsWait);
+ refreshVoiceEffectLists(true);
+ }
+ else
+ {
+ // If voice effects are disabled, pretend we've received them and carry on.
+ setState(stateVoiceFontsReceived);
+ }
+
// request the current set of block rules (we'll need them when updating the friends list)
accountListBlockRulesSendMessage();
@@ -1253,12 +1369,25 @@ void LLVivoxVoiceClient::stateMachine()
writeString(stream.str());
}
}
+ break;
+
+ //MARK: stateVoiceFontsWait
+ case stateVoiceFontsWait: // Await voice font list
+ // accountGetSessionFontsResponse() will transition from here to
+ // stateVoiceFontsReceived, to ensure we have the voice font list
+ // before attempting to create a session.
+ break;
+ //MARK: stateVoiceFontsReceived
+ case stateVoiceFontsReceived: // Voice font list received
+ // Set up the timer to check for expiring voice fonts
+ mVoiceFontExpiryTimer.start();
+ mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+
#if USE_SESSION_GROUPS
// create the main session group
- sessionGroupCreateSendMessage();
-
setState(stateCreatingSessionGroup);
+ sessionGroupCreateSendMessage();
#else
// Not using session groups -- skip the stateCreatingSessionGroup state.
setState(stateNoChannel);
@@ -1306,6 +1435,10 @@ void LLVivoxVoiceClient::stateMachine()
mTuningExitState = stateNoChannel;
setState(stateMicTuningStart);
}
+ else if(mCaptureBufferMode)
+ {
+ setState(stateCaptureBufferPaused);
+ }
else if(sessionNeedsRelog(mNextAudioSession))
{
requestRelog();
@@ -1316,6 +1449,7 @@ void LLVivoxVoiceClient::stateMachine()
sessionState *oldSession = mAudioSession;
mAudioSession = mNextAudioSession;
+ mAudioSessionChanged = true;
if(!mAudioSession->mReconnect)
{
mNextAudioSession = NULL;
@@ -1478,6 +1612,13 @@ void LLVivoxVoiceClient::stateMachine()
enforceTether();
}
+ // Do notifications for expiring Voice Fonts.
+ if (mVoiceFontExpiryTimer.hasExpired())
+ {
+ expireVoiceFonts();
+ mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+ }
+
// Send an update only if the ptt or mute 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.
// Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged.
@@ -1547,6 +1688,8 @@ void LLVivoxVoiceClient::stateMachine()
mAccountHandle.clear();
deleteAllSessions();
deleteAllBuddies();
+ deleteAllVoiceFonts();
+ deleteVoiceFontTemplates();
if(mVoiceEnabled && !mRelogRequested)
{
@@ -1627,15 +1770,15 @@ void LLVivoxVoiceClient::stateMachine()
}
- if(mAudioSession && mAudioSession->mParticipantsChanged)
+ if (mAudioSessionChanged)
{
- mAudioSession->mParticipantsChanged = false;
- mAudioSessionChanged = true;
+ mAudioSessionChanged = false;
+ notifyParticipantObservers();
+ notifyVoiceFontObservers();
}
-
- if(mAudioSessionChanged)
+ else if (mAudioSession && mAudioSession->mParticipantsChanged)
{
- mAudioSessionChanged = false;
+ mAudioSession->mParticipantsChanged = false;
notifyParticipantObservers();
}
}
@@ -1751,8 +1894,11 @@ void LLVivoxVoiceClient::sessionGroupCreateSendMessage()
void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText)
{
- LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
-
+ LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL;
+
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
+
session->mCreateInProgress = true;
if(startAudio)
{
@@ -1776,10 +1922,11 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool st
<< "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"
<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
}
-
+
stream
<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+ << "<VoiceFontID>" << font_index << "</VoiceFontID>"
<< "<Name>" << mChannelName << "</Name>"
<< "</Request>\n\n\n";
writeString(stream.str());
@@ -1787,8 +1934,11 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool st
void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)
{
- LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
-
+ LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL;
+
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
+
session->mCreateInProgress = true;
if(startAudio)
{
@@ -1814,6 +1964,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session
<< "<Name>" << mChannelName << "</Name>"
<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+ << "<VoiceFontID>" << font_index << "</VoiceFontID>"
<< "<Password>" << password << "</Password>"
<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"
<< "</Request>\n\n\n"
@@ -1824,7 +1975,10 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session
void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
{
- LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL;
+ LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle << LL_ENDL;
+
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
session->mMediaConnectInProgress = true;
@@ -1834,6 +1988,7 @@ void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
<< "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">"
<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<VoiceFontID>" << font_index << "</VoiceFontID>"
<< "<Media>Audio</Media>"
<< "</Request>\n\n\n";
@@ -3156,7 +3311,7 @@ void LLVivoxVoiceClient::sessionAddedEvent(
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->mCallerID.generate(session->mSIPURI);
session->mSynthesizedCallerID = true;
// Can't look up the name in this case -- we have to extract it from the URI.
@@ -3434,6 +3589,26 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
}
}
+void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType)
+{
+ if (mediaCompletionType == "AuxBufferAudioCapture")
+ {
+ mCaptureBufferRecording = false;
+ }
+ else if (mediaCompletionType == "AuxBufferAudioRender")
+ {
+ // Ignore all but the last stop event
+ if (--mPlayRequestCount <= 0)
+ {
+ mCaptureBufferPlaying = false;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL;
+ }
+}
+
void LLVivoxVoiceClient::mediaStreamUpdatedEvent(
std::string &sessionHandle,
std::string &sessionGroupHandle,
@@ -4142,8 +4317,8 @@ LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::addParti
else
{
// Create a UUID by hashing the URI, but do NOT set mAvatarIDValid.
- // This tells code in LLVivoxVoiceClient that the ID will not be in the name cache.
- setUUIDFromStringHash(result->mAvatarID, uri);
+ // This indicates that the ID will not be in the name cache.
+ result->mAvatarID.generate(uri);
}
}
@@ -4638,7 +4813,7 @@ BOOL LLVivoxVoiceClient::isOnlineSIP(const LLUUID &id)
return result;
}
-bool LLVivoxVoiceClient::isVoiceWorking()
+bool LLVivoxVoiceClient::isVoiceWorking() const
{
//Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758)
// Condition with joining spatial num was added to take into account possible problems with connection to voice
@@ -5659,7 +5834,12 @@ LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::addSession(const std::stri
result = new sessionState();
result->mSIPURI = uri;
result->mHandle = handle;
-
+
+ if (LLVoiceClient::instance().getVoiceEffectEnabled())
+ {
+ result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault();
+ }
+
mSessions.insert(result);
if(!result->mHandle.empty())
@@ -6084,8 +6264,8 @@ void LLVivoxVoiceClient::notifyParticipantObservers()
)
{
LLVoiceClientParticipantObserver* observer = *it;
- observer->onChange();
- // In case onChange() deleted an entry.
+ observer->onParticipantsChanged();
+ // In case onParticipantsChanged() deleted an entry.
it = mParticipantObservers.upper_bound(observer);
}
}
@@ -6248,6 +6428,660 @@ void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string
}
}
+bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id)
+{
+ if (!mAudioSession)
+ {
+ return false;
+ }
+
+ if (!id.isNull())
+ {
+ if (mVoiceFontMap.empty())
+ {
+ LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL;
+ return false;
+ }
+ else if (mVoiceFontMap.find(id) == mVoiceFontMap.end())
+ {
+ LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL;
+ return false;
+ }
+ }
+
+ // *TODO: Check for expired fonts?
+ mAudioSession->mVoiceFontID = id;
+
+ // *TODO: Separate voice font defaults for spatial chat and IM?
+ gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString());
+
+ sessionSetVoiceFontSendMessage(mAudioSession);
+ notifyVoiceFontObservers();
+
+ return true;
+}
+
+const LLUUID LLVivoxVoiceClient::getVoiceEffect()
+{
+ return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null;
+}
+
+LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id)
+{
+ LLSD sd;
+
+ voice_font_map_t::iterator iter = mVoiceFontMap.find(id);
+ if (iter != mVoiceFontMap.end())
+ {
+ sd["template_only"] = false;
+ }
+ else
+ {
+ // Voice effect is not in the voice font map, see if there is a template
+ iter = mVoiceFontTemplateMap.find(id);
+ if (iter == mVoiceFontTemplateMap.end())
+ {
+ LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL;
+ return sd;
+ }
+ sd["template_only"] = true;
+ }
+
+ voiceFontEntry *font = iter->second;
+ sd["name"] = font->mName;
+ sd["expiry_date"] = font->mExpirationDate;
+ sd["is_new"] = font->mIsNew;
+
+ return sd;
+}
+
+LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) :
+ mID(id),
+ mFontIndex(0),
+ mFontType(VOICE_FONT_TYPE_NONE),
+ mFontStatus(VOICE_FONT_STATUS_NONE),
+ mIsNew(false)
+{
+ mExpiryTimer.stop();
+ mExpiryWarningTimer.stop();
+}
+
+LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry()
+{
+}
+
+void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists)
+{
+ if (clear_lists)
+ {
+ mVoiceFontsReceived = false;
+ deleteAllVoiceFonts();
+ deleteVoiceFontTemplates();
+ }
+
+ accountGetSessionFontsSendMessage();
+ accountGetTemplateFontsSendMessage();
+}
+
+const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const
+{
+ return mVoiceFontList;
+}
+
+const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const
+{
+ return mVoiceFontTemplateList;
+}
+
+void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
+ const std::string &name,
+ const std::string &description,
+ const LLDate &expiration_date,
+ bool has_expired,
+ const S32 font_type,
+ const S32 font_status,
+ const bool template_font)
+{
+ // Vivox SessionFontIDs are not guaranteed to remain the same between
+ // sessions or grids so use a UUID for the name.
+
+ // If received name is not a UUID, fudge one by hashing the name and type.
+ LLUUID font_id;
+ if (LLUUID::validate(name))
+ {
+ font_id = LLUUID(name);
+ }
+ else
+ {
+ font_id.generate(STRINGIZE(font_type << ":" << name));
+ }
+
+ voiceFontEntry *font = NULL;
+
+ voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap;
+ voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList;
+
+ // Check whether we've seen this font before.
+ voice_font_map_t::iterator iter = font_map.find(font_id);
+ bool new_font = (iter == font_map.end());
+
+ // Override the has_expired flag if we have passed the expiration_date as a double check.
+ if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL))
+ {
+ has_expired = true;
+ }
+
+ if (has_expired)
+ {
+ LL_DEBUGS("Voice") << "Expired " << (template_font ? "Template " : "")
+ << expiration_date.asString() << " " << font_id
+ << " (" << font_index << ") " << name << LL_ENDL;
+
+ // Remove existing session fonts that have expired since we last saw them.
+ if (!new_font && !template_font)
+ {
+ deleteVoiceFont(font_id);
+ }
+ return;
+ }
+
+ if (new_font)
+ {
+ // If it is a new font create a new entry.
+ font = new voiceFontEntry(font_id);
+ }
+ else
+ {
+ // Not a new font, update the existing entry
+ font = iter->second;
+ }
+
+ if (font)
+ {
+ font->mFontIndex = font_index;
+ // Use the description for the human readable name if available, as the
+ // "name" may be a UUID.
+ font->mName = description.empty() ? name : description;
+ font->mFontType = font_type;
+ font->mFontStatus = font_status;
+
+ // If the font is new or the expiration date has changed the expiry timers need updating.
+ if (!template_font && (new_font || font->mExpirationDate != expiration_date))
+ {
+ font->mExpirationDate = expiration_date;
+
+ // Set the expiry timer to trigger a notification when the voice font can no longer be used.
+ font->mExpiryTimer.start();
+ font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL);
+
+ // Set the warning timer to some interval before actual expiry.
+ S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime");
+ if (warning_time != 0)
+ {
+ font->mExpiryWarningTimer.start();
+ F64 expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time);
+ font->mExpiryWarningTimer.setExpiryAt(expiry_time - VOICE_FONT_EXPIRY_INTERVAL);
+ }
+ else
+ {
+ // Disable the warning timer.
+ font->mExpiryWarningTimer.stop();
+ }
+
+ // Only flag new session fonts after the first time we have fetched the list.
+ if (mVoiceFontsReceived)
+ {
+ font->mIsNew = true;
+ mVoiceFontsNew = true;
+ }
+ }
+
+ LL_DEBUGS("Voice") << (template_font ? "Template " : "")
+ << font->mExpirationDate.asString() << " " << font->mID
+ << " (" << font->mFontIndex << ") " << name << LL_ENDL;
+
+ if (new_font)
+ {
+ font_map.insert(voice_font_map_t::value_type(font->mID, font));
+ font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID));
+ }
+
+ mVoiceFontListDirty = true;
+
+ // Debugging stuff
+
+ if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN)
+ {
+ LL_DEBUGS("Voice") << "Unknown voice font type: " << font_type << LL_ENDL;
+ }
+ if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN)
+ {
+ LL_DEBUGS("Voice") << "Unknown voice font status: " << font_status << LL_ENDL;
+ }
+ }
+}
+
+void LLVivoxVoiceClient::expireVoiceFonts()
+{
+ // *TODO: If we are selling voice fonts in packs, there are probably
+ // going to be a number of fonts with the same expiration time, so would
+ // be more efficient to just keep a list of expiration times rather
+ // than checking each font individually.
+
+ bool have_expired = false;
+ bool will_expire = false;
+ bool expired_in_use = false;
+
+ LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault();
+
+ voice_font_map_t::iterator iter;
+ for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+ {
+ voiceFontEntry* voice_font = iter->second;
+ LLFrameTimer& expiry_timer = voice_font->mExpiryTimer;
+ LLFrameTimer& warning_timer = voice_font->mExpiryWarningTimer;
+
+ // Check for expired voice fonts
+ if (expiry_timer.getStarted() && expiry_timer.hasExpired())
+ {
+ // Check whether it is the active voice font
+ if (voice_font->mID == current_effect)
+ {
+ // Reset to no voice effect.
+ setVoiceEffect(LLUUID::null);
+ expired_in_use = true;
+ }
+
+ LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL;
+ deleteVoiceFont(voice_font->mID);
+ have_expired = true;
+ }
+
+ // Check for voice fonts that will expire in less that the warning time
+ if (warning_timer.getStarted() && warning_timer.hasExpired())
+ {
+ LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " will expire soon." << LL_ENDL;
+ will_expire = true;
+ warning_timer.stop();
+ }
+ }
+
+ LLSD args;
+ args["URL"] = LLTrans::getString("voice_morphing_url");
+
+ // Give a notification if any voice fonts have expired.
+ if (have_expired)
+ {
+ if (expired_in_use)
+ {
+ LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args);
+ }
+ else
+ {
+ LLNotificationsUtil::add("VoiceEffectsExpired", args);
+ }
+
+ // Refresh voice font lists in the UI.
+ notifyVoiceFontObservers();
+ }
+
+ // Give a warning notification if any voice fonts are due to expire.
+ if (will_expire)
+ {
+ S32 seconds = gSavedSettings.getS32("VoiceEffectExpiryWarningTime");
+ args["INTERVAL"] = llformat("%d", seconds / SEC_PER_DAY);
+
+ LLNotificationsUtil::add("VoiceEffectsWillExpire", args);
+ }
+}
+
+void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id)
+{
+ // Remove the entry from the voice font list.
+ voice_effect_list_t::iterator list_iter = mVoiceFontList.begin();
+ while (list_iter != mVoiceFontList.end())
+ {
+ if (list_iter->second == id)
+ {
+ LL_DEBUGS("Voice") << "Removing " << id << " from the voice font list." << LL_ENDL;
+ mVoiceFontList.erase(list_iter++);
+ mVoiceFontListDirty = true;
+ }
+ else
+ {
+ ++list_iter;
+ }
+ }
+
+ // Find the entry in the voice font map and erase its data.
+ voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id);
+ if (map_iter != mVoiceFontMap.end())
+ {
+ delete map_iter->second;
+ }
+
+ // Remove the entry from the voice font map.
+ mVoiceFontMap.erase(map_iter);
+}
+
+void LLVivoxVoiceClient::deleteAllVoiceFonts()
+{
+ mVoiceFontList.clear();
+
+ voice_font_map_t::iterator iter;
+ for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+ {
+ delete iter->second;
+ }
+ mVoiceFontMap.clear();
+}
+
+void LLVivoxVoiceClient::deleteVoiceFontTemplates()
+{
+ mVoiceFontTemplateList.clear();
+
+ voice_font_map_t::iterator iter;
+ for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter)
+ {
+ delete iter->second;
+ }
+ mVoiceFontTemplateMap.clear();
+}
+
+S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const
+{
+ S32 result = 0;
+ if (!id.isNull())
+ {
+ voice_font_map_t::const_iterator it = mVoiceFontMap.find(id);
+ if (it != mVoiceFontMap.end())
+ {
+ result = it->second->mFontIndex;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Selected voice font " << id << " is not available." << LL_ENDL;
+ }
+ }
+ return result;
+}
+
+S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const
+{
+ S32 result = 0;
+ if (!id.isNull())
+ {
+ voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id);
+ if (it != mVoiceFontTemplateMap.end())
+ {
+ result = it->second->mFontIndex;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Selected voice font template " << id << " is not available." << LL_ENDL;
+ }
+ }
+ return result;
+}
+
+void LLVivoxVoiceClient::accountGetSessionFontsSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Requesting voice font list." << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetSessionFonts.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Requesting voice font template list." << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetTemplateFonts.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(sessionState *session)
+{
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL;
+
+ std::ostringstream stream;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetVoiceFont.1\">"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<SessionFontID>" << font_index << "</SessionFontID>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString)
+{
+ // Voice font list entries were updated via addVoiceFont() during parsing.
+ if(getState() == stateVoiceFontsWait)
+ {
+ setState(stateVoiceFontsReceived);
+ }
+
+ notifyVoiceFontObservers();
+ mVoiceFontsReceived = true;
+}
+
+void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString)
+{
+ // Voice font list entries were updated via addVoiceFont() during parsing.
+ notifyVoiceFontObservers();
+}
+void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer)
+{
+ mVoiceFontObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer)
+{
+ mVoiceFontObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyVoiceFontObservers()
+{
+ LL_DEBUGS("Voice") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL;
+
+ for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin();
+ it != mVoiceFontObservers.end();
+ )
+ {
+ LLVoiceEffectObserver* observer = *it;
+ observer->onVoiceEffectChanged(mVoiceFontListDirty);
+ // In case onVoiceEffectChanged() deleted an entry.
+ it = mVoiceFontObservers.upper_bound(observer);
+ }
+ mVoiceFontListDirty = false;
+
+ // If new Voice Fonts have been added notify the user.
+ if (mVoiceFontsNew)
+ {
+ if(mVoiceFontsReceived)
+ {
+ LLNotificationsUtil::add("VoiceEffectsNew");
+ }
+ mVoiceFontsNew = false;
+ }
+}
+
+void LLVivoxVoiceClient::enablePreviewBuffer(bool enable)
+{
+ mCaptureBufferMode = enable;
+ if(mCaptureBufferMode && getState() >= stateNoChannel)
+ {
+ LL_DEBUGS("Voice") << "no channel" << LL_ENDL;
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::recordPreviewBuffer()
+{
+ if (!mCaptureBufferMode)
+ {
+ LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL;
+ mCaptureBufferRecording = false;
+ return;
+ }
+
+ mCaptureBufferRecording = true;
+}
+
+void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
+{
+ if (!mCaptureBufferMode)
+ {
+ LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL;
+ mCaptureBufferRecording = false;
+ return;
+ }
+
+ if (!mCaptureBufferRecorded)
+ {
+ // Can't play until we have something recorded!
+ mCaptureBufferPlaying = false;
+ return;
+ }
+
+ mPreviewVoiceFont = effect_id;
+ mCaptureBufferPlaying = true;
+}
+
+void LLVivoxVoiceClient::stopPreviewBuffer()
+{
+ mCaptureBufferRecording = false;
+ mCaptureBufferPlaying = false;
+}
+
+bool LLVivoxVoiceClient::isPreviewRecording()
+{
+ return (mCaptureBufferMode && mCaptureBufferRecording);
+}
+
+bool LLVivoxVoiceClient::isPreviewPlaying()
+{
+ return (mCaptureBufferMode && mCaptureBufferPlaying);
+}
+
+void LLVivoxVoiceClient::captureBufferRecordStartSendMessage()
+{ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL;
+
+ // Start capture
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.StartBufferCapture.1\">"
+ << "</Request>"
+ << "\n\n\n";
+
+ // Unmute the mic
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<Value>false</Value>"
+ << "</Request>\n\n\n";
+
+ // Dirty the PTT state so that it will get reset when we finishing previewing
+ mPTTDirty = true;
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::captureBufferRecordStopSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL;
+
+ // Mute the mic. PTT state was dirtied at recording start, so will be reset when finished previewing.
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+ << "<Value>true</Value>"
+ << "</Request>\n\n\n";
+
+ // Stop capture
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id)
+{
+ if(!mAccountHandle.empty())
+ {
+ // Track how may play requests are sent, so we know how many stop events to
+ // expect before play actually stops.
+ ++mPlayRequestCount;
+
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL;
+
+ S32 font_index = getVoiceFontTemplateIndex(voice_font_id);
+ LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.PlayAudioBuffer.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "<TemplateFontID>" << font_index << "</TemplateFontID>"
+ << "<FontDelta />"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::captureBufferPlayStopSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
+ << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
LLVivoxProtocolParser::LLVivoxProtocolParser()
{
@@ -6466,7 +7300,30 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
{
LLVivoxVoiceClient::getInstance()->deleteAllAutoAcceptRules();
}
-
+ else if (!stricmp("SessionFont", tag))
+ {
+ id = 0;
+ nameString.clear();
+ descriptionString.clear();
+ expirationDate = LLDate();
+ hasExpired = false;
+ fontType = 0;
+ fontStatus = 0;
+ }
+ else if (!stricmp("TemplateFont", tag))
+ {
+ id = 0;
+ nameString.clear();
+ descriptionString.clear();
+ expirationDate = LLDate();
+ hasExpired = false;
+ fontType = 0;
+ fontStatus = 0;
+ }
+ else if (!stricmp("MediaCompletionType", tag))
+ {
+ mediaCompletionType.clear();
+ }
}
}
responseDepth++;
@@ -6612,8 +7469,43 @@ void LLVivoxProtocolParser::EndTag(const char *tag)
subscriptionHandle = string;
else if (!stricmp("SubscriptionType", tag))
subscriptionType = string;
-
-
+ else if (!stricmp("SessionFont", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false);
+ }
+ else if (!stricmp("TemplateFont", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true);
+ }
+ else if (!stricmp("ID", tag))
+ {
+ id = strtol(string.c_str(), NULL, 10);
+ }
+ else if (!stricmp("Description", tag))
+ {
+ descriptionString = string;
+ }
+ else if (!stricmp("ExpirationDate", tag))
+ {
+ expirationDate = expiryTimeStampToLLDate(string);
+ }
+ else if (!stricmp("Expired", tag))
+ {
+ hasExpired = !stricmp(string.c_str(), "1");
+ }
+ else if (!stricmp("Type", tag))
+ {
+ fontType = strtol(string.c_str(), NULL, 10);
+ }
+ else if (!stricmp("Status", tag))
+ {
+ fontStatus = strtol(string.c_str(), NULL, 10);
+ }
+ else if (!stricmp("MediaCompletionType", tag))
+ {
+ mediaCompletionType = string;;
+ }
+
textBuffer.clear();
accumulateText= false;
@@ -6642,6 +7534,21 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length)
// --------------------------------------------------------------------------------
+LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts)
+{
+ // *HACK: Vivox reports the time incorrectly. LLDate also only parses a
+ // subset of valid ISO 8601 dates (only handles Z, not offsets).
+ // So just use the date portion and fix the time here.
+ std::string time_stamp = vivox_ts.substr(0, 10);
+ time_stamp += VOICE_FONT_EXPIRY_TIME;
+
+ LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL;
+
+ return LLDate(time_stamp);
+}
+
+// --------------------------------------------------------------------------------
+
void LLVivoxProtocolParser::processResponse(std::string tag)
{
LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL;
@@ -6695,7 +7602,17 @@ void LLVivoxProtocolParser::processResponse(std::string tag)
</Event>
*/
LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
- }
+ }
+ else if (!stricmp(eventTypeCstr, "MediaCompletionEvent"))
+ {
+ /*
+ <Event type="MediaCompletionEvent">
+ <SessionGroupHandle />
+ <MediaCompletionType>AuxBufferAudioCapture</MediaCompletionType>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType);
+ }
else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent"))
{
/*
@@ -6756,6 +7673,9 @@ void LLVivoxProtocolParser::processResponse(std::string tag)
}
else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
{
+ // These are really spammy in tuning mode
+ squelchDebugOutput = true;
+
LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy);
}
else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent"))
@@ -6870,6 +7790,14 @@ void LLVivoxProtocolParser::processResponse(std::string tag)
// 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.GetSessionFonts.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString);
+ }
/*
else if (!stricmp(actionCstr, "Account.ChannelGetList.1"))
{
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
index 59fec8b954..f858f8f74e 100644
--- a/indra/newview/llvoicevivox.h
+++ b/indra/newview/llvoicevivox.h
@@ -57,15 +57,9 @@ class LLVivoxVoiceClientMuteListObserver;
class LLVivoxVoiceClientFriendsObserver;
-class LLVivoxVoiceClientParticipantObserver
-{
-public:
- virtual ~LLVivoxVoiceClientParticipantObserver() { }
- virtual void onChange() = 0;
-};
-
-
-class LLVivoxVoiceClient: public LLSingleton<LLVivoxVoiceClient>, virtual public LLVoiceModuleInterface
+class LLVivoxVoiceClient : public LLSingleton<LLVivoxVoiceClient>,
+ virtual public LLVoiceModuleInterface,
+ virtual public LLVoiceEffectInterface
{
LOG_CLASS(LLVivoxVoiceClient);
public:
@@ -84,7 +78,7 @@ public:
virtual void updateSettings(); // call after loading settings and whenever they change
// Returns true if vivox has successfully logged in and is not in error state
- virtual bool isVoiceWorking();
+ virtual bool isVoiceWorking() const;
/////////////////////
/// @name Tuning
@@ -232,15 +226,49 @@ public:
virtual void removeObserver(LLFriendObserver* observer);
virtual void addObserver(LLVoiceClientParticipantObserver* observer);
virtual void removeObserver(LLVoiceClientParticipantObserver* observer);
-
-
-
//@}
virtual std::string sipURIFromID(const LLUUID &id);
//@}
-
+ /// @name LLVoiceEffectInterface virtual implementations
+ /// @see LLVoiceEffectInterface
+ //@{
+
+ //////////////////////////
+ /// @name Accessors
+ //@{
+ virtual bool setVoiceEffect(const LLUUID& id);
+ virtual const LLUUID getVoiceEffect();
+ virtual LLSD getVoiceEffectProperties(const LLUUID& id);
+
+ virtual void refreshVoiceEffectLists(bool clear_lists);
+ virtual const voice_effect_list_t& getVoiceEffectList() const;
+ virtual const voice_effect_list_t& getVoiceEffectTemplateList() const;
+ //@}
+
+ //////////////////////////////
+ /// @name Status notification
+ //@{
+ virtual void addObserver(LLVoiceEffectObserver* observer);
+ virtual void removeObserver(LLVoiceEffectObserver* observer);
+ //@}
+
+ //////////////////////////////
+ /// @name Effect preview buffer
+ //@{
+ virtual void enablePreviewBuffer(bool enable);
+ virtual void recordPreviewBuffer();
+ virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null);
+ virtual void stopPreviewBuffer();
+
+ virtual bool isPreviewRecording();
+ virtual bool isPreviewPlaying();
+ //@}
+
+ //@}
+
+
protected:
//////////////////////
// Vivox Specific definitions
@@ -278,14 +306,13 @@ protected:
bool mIsSpeaking;
bool mIsModeratorMuted;
bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted)
- bool mVolumeSet; // true if incoming volume messages should not change the volume
+ bool mVolumeSet; // true if incoming volume messages should not change the volume
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
@@ -332,14 +359,17 @@ protected:
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.
+
+ // Set to true when the volume/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 mMuteDirty;
-
+ bool mMuteDirty;
+
bool mParticipantsChanged;
participantMap mParticipantsByURI;
participantUUIDMap mParticipantsByUUID;
+
+ LLUUID mVoiceFontID;
};
// internal state for a simple state machine. This is used to deal with the asynchronous nature of some of the messages.
@@ -356,6 +386,11 @@ protected:
stateMicTuningStart,
stateMicTuningRunning,
stateMicTuningStop,
+ stateCaptureBufferPaused,
+ stateCaptureBufferRecStart,
+ stateCaptureBufferRecording,
+ stateCaptureBufferPlayStart,
+ stateCaptureBufferPlaying,
stateConnectorStart, // connector needs to be started
stateConnectorStarting, // waiting for connector handle
stateConnectorStarted, // connector handle received
@@ -364,6 +399,8 @@ protected:
stateNeedsLogin, // send login request
stateLoggingIn, // waiting for account handle
stateLoggedIn, // account handle received
+ stateVoiceFontsWait, // Awaiting the list of voice fonts
+ stateVoiceFontsReceived, // List of voice fonts received
stateCreatingSessionGroup, // Creating the main session group
stateNoChannel, //
stateJoiningSession, // waiting for session handle
@@ -436,8 +473,6 @@ protected:
void tuningCaptureStartSendMessage(int duration);
void tuningCaptureStopSendMessage();
- bool inTuningStates();
-
//----------------------------------
// devices
void clearCaptureDevices();
@@ -464,6 +499,7 @@ protected:
void connectorShutdownResponse(int statusCode, std::string &statusString);
void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state);
+ void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType);
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);
@@ -591,8 +627,8 @@ protected:
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);
-
+ void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);
+
/////////////////////////////
// session control messages
@@ -621,7 +657,21 @@ protected:
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);
-
+
+ /////////////////////////////
+ // Voice fonts
+
+ void addVoiceFont(const S32 id,
+ const std::string &name,
+ const std::string &description,
+ const LLDate &expiration_date,
+ bool has_expired,
+ const S32 font_type,
+ const S32 font_status,
+ const bool template_font = false);
+ void accountGetSessionFontsResponse(int statusCode, const std::string &statusString);
+ void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString);
+
private:
LLVoiceVersionInfo mVoiceVersion;
@@ -804,6 +854,88 @@ private:
typedef std::set<LLFriendObserver*> friend_observer_set_t;
friend_observer_set_t mFriendObservers;
void notifyFriendObservers();
+
+ // Voice Fonts
+
+ void expireVoiceFonts();
+ void deleteVoiceFont(const LLUUID& id);
+ void deleteAllVoiceFonts();
+ void deleteVoiceFontTemplates();
+
+ S32 getVoiceFontIndex(const LLUUID& id) const;
+ S32 getVoiceFontTemplateIndex(const LLUUID& id) const;
+
+ void accountGetSessionFontsSendMessage();
+ void accountGetTemplateFontsSendMessage();
+ void sessionSetVoiceFontSendMessage(sessionState *session);
+
+ void notifyVoiceFontObservers();
+
+ typedef enum e_voice_font_type
+ {
+ VOICE_FONT_TYPE_NONE = 0,
+ VOICE_FONT_TYPE_ROOT = 1,
+ VOICE_FONT_TYPE_USER = 2,
+ VOICE_FONT_TYPE_UNKNOWN
+ } EVoiceFontType;
+
+ typedef enum e_voice_font_status
+ {
+ VOICE_FONT_STATUS_NONE = 0,
+ VOICE_FONT_STATUS_FREE = 1,
+ VOICE_FONT_STATUS_NOT_FREE = 2,
+ VOICE_FONT_STATUS_UNKNOWN
+ } EVoiceFontStatus;
+
+ struct voiceFontEntry
+ {
+ voiceFontEntry(LLUUID& id);
+ ~voiceFontEntry();
+
+ LLUUID mID;
+ S32 mFontIndex;
+ std::string mName;
+ LLDate mExpirationDate;
+ S32 mFontType;
+ S32 mFontStatus;
+ bool mIsNew;
+
+ LLFrameTimer mExpiryTimer;
+ LLFrameTimer mExpiryWarningTimer;
+ };
+
+ bool mVoiceFontsReceived;
+ bool mVoiceFontsNew;
+ bool mVoiceFontListDirty;
+ voice_effect_list_t mVoiceFontList;
+ voice_effect_list_t mVoiceFontTemplateList;
+
+ typedef std::map<const LLUUID, voiceFontEntry*> voice_font_map_t;
+ voice_font_map_t mVoiceFontMap;
+ voice_font_map_t mVoiceFontTemplateMap;
+
+ typedef std::set<LLVoiceEffectObserver*> voice_font_observer_set_t;
+ voice_font_observer_set_t mVoiceFontObservers;
+
+ LLFrameTimer mVoiceFontExpiryTimer;
+
+
+ // Audio capture buffer
+
+ void captureBufferRecordStartSendMessage();
+ void captureBufferRecordStopSendMessage();
+ void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null);
+ void captureBufferPlayStopSendMessage();
+
+ bool mCaptureBufferMode; // Disconnected from voice channels while using the capture buffer.
+ bool mCaptureBufferRecording; // A voice sample is being captured.
+ bool mCaptureBufferRecorded; // A voice sample is captured in the buffer ready to play.
+ bool mCaptureBufferPlaying; // A voice sample is being played.
+
+ LLTimer mCaptureTimer;
+ LLUUID mPreviewVoiceFont;
+ LLUUID mPreviewVoiceFontLast;
+ S32 mPlayRequestCount;
};
/**
@@ -890,7 +1022,13 @@ protected:
int numberOfAliases;
std::string subscriptionHandle;
std::string subscriptionType;
-
+ S32 id;
+ std::string descriptionString;
+ LLDate expirationDate;
+ bool hasExpired;
+ S32 fontType;
+ S32 fontStatus;
+ std::string mediaCompletionType;
// Members for processing text between tags
std::string textBuffer;
@@ -907,11 +1045,9 @@ protected:
void StartTag(const char *tag, const char **attr);
void EndTag(const char *tag);
void CharData(const char *buffer, int length);
-
+ LLDate expiryTimeStampToLLDate(const std::string& vivox_ts);
};
#endif //LL_VIVOX_VOICE_CLIENT_H
-
-
diff --git a/indra/newview/skins/default/xui/en/floater_voice_controls.xml b/indra/newview/skins/default/xui/en/floater_voice_controls.xml
index 5b77f11d71..0569b4d515 100644
--- a/indra/newview/skins/default/xui/en/floater_voice_controls.xml
+++ b/indra/newview/skins/default/xui/en/floater_voice_controls.xml
@@ -3,7 +3,7 @@
can_resize="true"
can_minimize="true"
can_close="false"
- height="202"
+ height="205"
layout="topleft"
min_height="124"
min_width="190"
@@ -50,7 +50,7 @@
user_resize="false"
auto_resize="false"
layout="topleft"
- height="26"
+ height="20"
name="my_panel">
<avatar_icon
enabled="false"
@@ -86,23 +86,38 @@
visible="true"
width="20" />
</layout_panel>
- <layout_panel
- auto_resize="false"
- user_resize="false"
- follows="top|left"
- height="26"
- visible="true"
- layout="topleft"
- name="leave_call_btn_panel"
- width="100">
- <button
- follows="right|top"
- height="23"
- top_pad="0"
- label="Leave Call"
- name="leave_call_btn"
- width="100" />
- </layout_panel>
+ <layout_stack
+ clip="true"
+ auto_resize="false"
+ follows="left|top|right"
+ height="26"
+ layout="topleft"
+ mouse_opaque="false"
+ name="voice_effect_and_leave_call_stack"
+ orientation="horizontal"
+ width="262">
+ <panel
+ class="panel_voice_effect"
+ name="panel_voice_effect"
+ visiblity_control="VoiceMorphingEnabled"
+ filename="panel_voice_effect.xml" />
+ <layout_panel
+ auto_resize="false"
+ user_resize="false"
+ follows="top|right"
+ height="23"
+ visible="true"
+ layout="topleft"
+ name="leave_call_btn_panel"
+ width="100">
+ <button
+ follows="right|top"
+ height="23"
+ label="Leave Call"
+ name="leave_call_btn"
+ width="100" />
+ </layout_panel>
+ </layout_stack>
<layout_panel
follows="all"
layout="topleft"
diff --git a/indra/newview/skins/default/xui/en/floater_voice_effect.xml b/indra/newview/skins/default/xui/en/floater_voice_effect.xml
new file mode 100644
index 0000000000..edc25348e4
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_voice_effect.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ can_resize="true"
+ height="420"
+ name="voice_effects"
+ help_topic="voice_effects"
+ title="PREVIEW VOICE MORPHING"
+ background_visible="true"
+ follows="all"
+ label="Places"
+ layout="topleft"
+ min_height="350"
+ min_width="330"
+ width="455">
+ <string name="no_voice_effect">
+ (No Voice Morph)
+ </string>
+ <string name="active_voice_effect">
+ (Active)
+ </string>
+ <string name="unsubscribed_voice_effect">
+ (Unsubscribed)
+ </string>
+ <string name="new_voice_effect">
+ (New!)
+ </string>
+ <text
+ height="68"
+ word_wrap="true"
+ use_ellipses="true"
+ type="string"
+ follows="left|top|right"
+ layout="topleft"
+ left="10"
+ name="status_text"
+ right="-10"
+ top="25">
+To preview any of the Voice Morphing effects, click the Record button to record a short snippet of voice, then click any Voice Morph in the list to hear how it will sound.
+
+To reconnect to Nearby Voice simply close this window.
+ </text>
+ <button
+ follows="left|top"
+ height="23"
+ label="Record Sample"
+ layout="topleft"
+ left="10"
+ name="record_btn"
+ tool_tip="Record a sample of your voice."
+ top_pad="5"
+ width="150">
+ <button.commit_callback
+ function="VoiceEffect.Record" />
+ </button>
+ <button
+ follows="left|top"
+ height="23"
+ label="Stop"
+ layout="topleft"
+ left_delta="0"
+ name="record_stop_btn"
+ top_delta="0"
+ width="150">
+ <button.commit_callback
+ function="VoiceEffect.Stop" />
+ </button>
+ <text
+ height="18"
+ halign="right"
+ use_ellipses="true"
+ type="string"
+ follows="left|top|right"
+ layout="topleft"
+ left_pad="10"
+ name="voice_morphing_link"
+ right="-10"
+ top_delta="5">
+ [[URL] Get Voice Morphing]
+ </text>
+ <scroll_list
+ bottom="-10"
+ draw_heading="true"
+ follows="all"
+ layout="topleft"
+ left="10"
+ multi_select="false"
+ name="voice_effect_list"
+ right="-10"
+ tool_tip="Record a sample of your voice, then click an effect to preview."
+ top="128">
+ <scroll_list.columns
+ label="Voice Morph"
+ name="name" relative_width="0.41"/>
+ <scroll_list.columns
+ dynamic_width="true"
+ label="Expires"
+ name="expires"
+ relative_width="0.59" />
+ </scroll_list>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index aff8f7ddf4..2641ce4ee4 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -80,6 +80,17 @@
function="Floater.Toggle"
parameter="gestures" />
</menu_item_check>
+ <menu_item_check
+ label="My Voice"
+ name="ShowVoice"
+ visibility_control="VoiceMorphingEnabled">
+ <menu_item_check.on_check
+ function="Floater.Visible"
+ parameter="voice_effect" />
+ <menu_item_check.on_click
+ function="Floater.Toggle"
+ parameter="voice_effect" />
+ </menu_item_check>
<menu
label="My Status"
name="Status"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 9af358eff3..6d3d0f13bf 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -6004,6 +6004,50 @@ We are creating a voice channel for you. This may take up to one minute.
</notification>
<notification
+ icon="notify.tga"
+ name="VoiceEffectsExpired"
+ sound="UISndAlert"
+ persist="true"
+ type="notify">
+One or more of your subscribed Voice Morphs has expired.
+[[URL] Click here] to renew your subscription.
+ <unique/>
+ </notification>
+
+ <notification
+ icon="notify.tga"
+ name="VoiceEffectsExpiredInUse"
+ sound="UISndAlert"
+ persist="true"
+ type="notify">
+The active Voice Morph has expired, your normal voice settings have been applied.
+[[URL] Click here] to renew your subscription.
+ <unique/>
+ </notification>
+
+ <notification
+ icon="notify.tga"
+ name="VoiceEffectsWillExpire"
+ sound="UISndAlert"
+ persist="true"
+ type="notify">
+One or more of your Voice Morphs will expire in less than [INTERVAL] days.
+[[URL] Click here] to renew your subscription.
+ <unique/>
+ </notification>
+ LLNotificationsUtil::add("VoiceEffectsNew");
+
+ <notification
+ icon="notify.tga"
+ name="VoiceEffectsNew"
+ sound="UISndAlert"
+ persist="true"
+ type="notify">
+New Voice Morphs are available!
+ <unique/>
+ </notification>
+
+ <notification
icon="notifytip.tga"
name="Cannot enter parcel: not a group member"
type="notifytip">
diff --git a/indra/newview/skins/default/xui/en/panel_voice_effect.xml b/indra/newview/skins/default/xui/en/panel_voice_effect.xml
new file mode 100644
index 0000000000..c575ca468c
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_voice_effect.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ follows="all"
+ height="26"
+ layout="topleft"
+ name="panel_voice_effect"
+ width="200">
+ <string name="no_voice_effect">
+ No Voice Morph
+ </string>
+ <string name="preview_voice_effects">
+ Preview Voice Morphing ▶
+ </string>
+ <string name="get_voice_effects">
+ Get Voice Morphing ▶
+ </string>
+ <combo_box
+ enabled="false"
+ follows="left|top|right"
+ height="23"
+ name="voice_effect"
+ tool_tip="Select a Voice Morphing effect to change your voice."
+ top_pad="0"
+ width="200">
+ <combo_box.item
+ label="No Voice Morph"
+ name="no_voice_effect"
+ top_pad="0"
+ value="0" />
+ <combo_box.commit_callback
+ function="Voice.CommitVoiceEffect" />
+ </combo_box>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 790bc64a4a..0d14a6b3c8 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -3093,6 +3093,8 @@ If you continue to receive this message, contact the [SUPPORT_SITE].
The session initialization is timed out
</string>
+ <string name="voice_morphing_url">http://secondlife.com/landing/voicemorphing</string>
+
<!-- Financial operations strings -->
<string name="paid_you_ldollars">[NAME] paid you L$[AMOUNT]</string>
<string name="you_paid_ldollars">You paid [NAME] L$[AMOUNT] [REASON].</string>