diff options
Diffstat (limited to 'indra')
25 files changed, 2155 insertions, 129 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 70caead451..fc9fe0fdd8 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/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 5ead756d20..0b996a758e 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -5981,6 +5981,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..a31ee67547 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/v0icem0rphingt3st</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> | 
