Files
SingularityViewer/indra/newview/llvoicechannel.cpp
Lirusaito c273e34ed8 [Voice Update] LLVoiceClient
Migrate a bunch of classes out of llvoiceclient.* and into new llvoicevivox.*
Update most of these to match their counterparts
Introduce VoiceFonts support (voice morphing, floater still to come)
Support for a bunch of v3 voice settings.
Move volume settings management from LLMutelist into LLSpeakerVolumeStorage
Support for Avaline mutes (WIP)
Adds voice section to LLAgent
Moved llfloatervoicedevicesettings to llpanelvoicedevicesettings, v3's voice device panel design is more intuitive.
2013-06-03 22:20:27 -04:00

883 lines
20 KiB
C++

/**
* @file llvoicechannel.cpp
* @brief Voice Channel related classes
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llagent.h"
#include "llhttpclient.h"
#include "llimview.h"
#include "llnotificationsutil.h"
#include "llvoicechannel.h"
extern AIHTTPTimeoutPolicy voiceCallCapResponder_timeout;
LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap;
LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap;
LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL;
LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL;
BOOL LLVoiceChannel::sSuspended = FALSE;
//
// Constants
//
const U32 DEFAULT_RETRIES_COUNT = 3;
class LLVoiceCallCapResponder : public LLHTTPClient::ResponderWithResult
{
public:
LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {};
/*virtual*/ void error(U32 status, const std::string& reason); // called with bad status codes
/*virtual*/ void result(const LLSD& content);
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return voiceCallCapResponder_timeout; }
/*virtual*/ char const* getName(void) const { return "LLVoiceCallCapResponder"; }
private:
LLUUID mSessionID;
};
void LLVoiceCallCapResponder::error(U32 status, const std::string& reason)
{
LL_WARNS("Voice") << "LLVoiceCallCapResponder::error [status:"
<< status << "]: " << reason << LL_ENDL;
LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID);
if ( channelp )
{
if ( 403 == status )
{
//403 == no ability
LLNotificationsUtil::add(
"VoiceNotAllowed",
channelp->getNotifyArgs());
}
else
{
LLNotificationsUtil::add(
"VoiceCallGenericError",
channelp->getNotifyArgs());
}
channelp->deactivate();
}
}
void LLVoiceCallCapResponder::result(const LLSD& content)
{
LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID);
if (channelp)
{
// *TODO: DEBUG SPAM
LLSD::map_const_iterator iter;
for(iter = content.beginMap(); iter != content.endMap(); ++iter)
{
LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got "
<< iter->first << LL_ENDL;
}
channelp->setChannelInfo(
content["voice_credentials"]["channel_uri"].asString(),
content["voice_credentials"]["channel_credentials"].asString());
}
}
//
// LLVoiceChannel
//
LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) :
mSessionID(session_id),
mState(STATE_NO_CHANNEL_INFO),
mSessionName(session_name),
mIgnoreNextSessionLeave(FALSE)
{
mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName;
if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second)
{
// a voice channel already exists for this session id, so this instance will be orphaned
// the end result should simply be the failure to make voice calls
LL_WARNS("Voice") << "Duplicate voice channels registered for session_id " << session_id << LL_ENDL;
}
LLVoiceClient::getInstance()->addObserver(this);
}
LLVoiceChannel::~LLVoiceChannel()
{
// Must check instance exists here, the singleton MAY have already been destroyed.
if(LLVoiceClient::instanceExists())
{
LLVoiceClient::getInstance()->removeObserver(this);
}
sVoiceChannelMap.erase(mSessionID);
sVoiceChannelURIMap.erase(mURI);
}
void LLVoiceChannel::setChannelInfo(
const std::string& uri,
const std::string& credentials)
{
setURI(uri);
mCredentials = credentials;
if (mState == STATE_NO_CHANNEL_INFO)
{
if (mURI.empty())
{
LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL;
deactivate();
}
else if (mCredentials.empty())
{
LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL;
deactivate();
}
else
{
setState(STATE_READY);
// if we are supposed to be active, reconnect
// this will happen on initial connect, as we request credentials on first use
if (sCurrentVoiceChannel == this)
{
// just in case we got new channel info while active
// should move over to new channel
activate();
}
}
}
}
void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal)
{
if (channelURI != mURI)
{
return;
}
if (type < BEGIN_ERROR_STATUS)
{
handleStatusChange(type);
}
else
{
handleError(type);
}
}
void LLVoiceChannel::handleStatusChange(EStatusType type)
{
// status updates
switch(type)
{
case STATUS_LOGIN_RETRY:
//mLoginNotificationHandle = LLNotifyBox::showXml("VoiceLoginRetry")->getHandle();
LLNotificationsUtil::add("VoiceLoginRetry");
break;
case STATUS_LOGGED_IN:
//if (!mLoginNotificationHandle.isDead())
//{
// LLNotifyBox* notifyp = (LLNotifyBox*)mLoginNotificationHandle.get();
// if (notifyp)
// {
// notifyp->close();
// }
// mLoginNotificationHandle.markDead();
//}
break;
case STATUS_LEFT_CHANNEL:
if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended)
{
// if forceably removed from channel
// update the UI and revert to default channel
LLNotificationsUtil::add("VoiceChannelDisconnected", mNotifyArgs);
deactivate();
}
mIgnoreNextSessionLeave = FALSE;
break;
case STATUS_JOINING:
if (callStarted())
{
setState(STATE_RINGING);
}
break;
case STATUS_JOINED:
if (callStarted())
{
setState(STATE_CONNECTED);
}
default:
break;
}
}
// default behavior is to just deactivate channel
// derived classes provide specific error messages
void LLVoiceChannel::handleError(EStatusType type)
{
deactivate();
setState(STATE_ERROR);
}
BOOL LLVoiceChannel::isActive()
{
// only considered active when currently bound channel matches what our channel
return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI;
}
BOOL LLVoiceChannel::callStarted()
{
return mState >= STATE_CALL_STARTED;
}
void LLVoiceChannel::deactivate()
{
if (mState >= STATE_RINGING)
{
// ignore session leave event
mIgnoreNextSessionLeave = TRUE;
}
if (callStarted())
{
setState(STATE_HUNG_UP);
//Default mic is OFF when leaving voice calls
if (gSavedSettings.getBOOL("AutoDisengageMic") &&
sCurrentVoiceChannel == this &&
LLVoiceClient::getInstance()->getUserPTTState())
{
gSavedSettings.setBOOL("PTTCurrentlyEnabled", true);
LLVoiceClient::getInstance()->inputUserControlState(true);
}
}
if (sCurrentVoiceChannel == this)
{
// default channel is proximal channel
sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance();
sCurrentVoiceChannel->activate();
}
}
void LLVoiceChannel::activate()
{
if (callStarted())
{
return;
}
// deactivate old channel and mark ourselves as the active one
if (sCurrentVoiceChannel != this)
{
// mark as current before deactivating the old channel to prevent
// activating the proximal channel between IM calls
LLVoiceChannel* old_channel = sCurrentVoiceChannel;
sCurrentVoiceChannel = this;
if (old_channel)
{
old_channel->deactivate();
}
}
if (mState == STATE_NO_CHANNEL_INFO)
{
// responsible for setting status to active
getChannelInfo();
}
else
{
setState(STATE_CALL_STARTED);
}
}
void LLVoiceChannel::getChannelInfo()
{
// pretend we have everything we need
if (sCurrentVoiceChannel == this)
{
setState(STATE_CALL_STARTED);
}
}
//static
LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id)
{
voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id);
if (found_it == sVoiceChannelMap.end())
{
return NULL;
}
else
{
return found_it->second;
}
}
//static
LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri)
{
voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri);
if (found_it == sVoiceChannelURIMap.end())
{
return NULL;
}
else
{
return found_it->second;
}
}
LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel()
{
return sCurrentVoiceChannel;
}
void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id)
{
sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID));
mSessionID = new_session_id;
sVoiceChannelMap.insert(std::make_pair(mSessionID, this));
}
void LLVoiceChannel::setURI(std::string uri)
{
sVoiceChannelURIMap.erase(mURI);
mURI = uri;
sVoiceChannelURIMap.insert(std::make_pair(mURI, this));
}
void LLVoiceChannel::setState(EState state)
{
switch(state)
{
case STATE_RINGING:
gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs);
break;
case STATE_CONNECTED:
gIMMgr->addSystemMessage(mSessionID, "connected", mNotifyArgs);
break;
case STATE_HUNG_UP:
gIMMgr->addSystemMessage(mSessionID, "hang_up", mNotifyArgs);
break;
default:
break;
}
mState = state;
}
//static
void LLVoiceChannel::initClass()
{
sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance();
}
//static
void LLVoiceChannel::suspend()
{
if (!sSuspended)
{
sSuspendedVoiceChannel = sCurrentVoiceChannel;
sSuspended = TRUE;
}
}
//static
void LLVoiceChannel::resume()
{
if (sSuspended)
{
if (LLVoiceClient::getInstance()->voiceEnabled())
{
if (sSuspendedVoiceChannel)
{
sSuspendedVoiceChannel->activate();
}
else
{
LLVoiceChannelProximal::getInstance()->activate();
}
}
sSuspended = FALSE;
}
}
//
// LLVoiceChannelGroup
//
LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) :
LLVoiceChannel(session_id, session_name)
{
mRetries = DEFAULT_RETRIES_COUNT;
mIsRetrying = FALSE;
}
void LLVoiceChannelGroup::deactivate()
{
if (callStarted())
{
LLVoiceClient::getInstance()->leaveNonSpatialChannel();
}
LLVoiceChannel::deactivate();
}
void LLVoiceChannelGroup::activate()
{
if (callStarted()) return;
LLVoiceChannel::activate();
if (callStarted())
{
// we have the channel info, just need to use it now
LLVoiceClient::getInstance()->setNonSpatialChannel(
mURI,
mCredentials);
//Mic default state is OFF on initiating/joining Ad-Hoc/Group calls
if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
{
LLVoiceClient::getInstance()->inputUserControlState(true);
}
}
}
void LLVoiceChannelGroup::getChannelInfo()
{
LLViewerRegion* region = gAgent.getRegion();
if (region)
{
std::string url = region->getCapability("ChatSessionRequest");
LLSD data;
data["method"] = "call";
data["session-id"] = mSessionID;
LLHTTPClient::post(url,
data,
new LLVoiceCallCapResponder(mSessionID));
}
}
void LLVoiceChannelGroup::setChannelInfo(
const std::string& uri,
const std::string& credentials)
{
setURI(uri);
mCredentials = credentials;
if (mState == STATE_NO_CHANNEL_INFO)
{
if(!mURI.empty() && !mCredentials.empty())
{
setState(STATE_READY);
// if we are supposed to be active, reconnect
// this will happen on initial connect, as we request credentials on first use
if (sCurrentVoiceChannel == this)
{
// just in case we got new channel info while active
// should move over to new channel
activate();
}
}
else
{
//*TODO: notify user
LL_WARNS("Voice") << "Received invalid credentials for channel " << mSessionName << LL_ENDL;
deactivate();
}
}
else if ( mIsRetrying )
{
// we have the channel info, just need to use it now
LLVoiceClient::getInstance()->setNonSpatialChannel(
mURI,
mCredentials);
}
}
void LLVoiceChannelGroup::handleStatusChange(EStatusType type)
{
// status updates
switch(type)
{
case STATUS_JOINED:
mRetries = 3;
mIsRetrying = FALSE;
default:
break;
}
LLVoiceChannel::handleStatusChange(type);
}
void LLVoiceChannelGroup::handleError(EStatusType status)
{
std::string notify;
switch(status)
{
case ERROR_CHANNEL_LOCKED:
case ERROR_CHANNEL_FULL:
notify = "VoiceChannelFull";
break;
case ERROR_NOT_AVAILABLE:
//clear URI and credentials
//set the state to be no info
//and activate
if ( mRetries > 0 )
{
mRetries--;
mIsRetrying = TRUE;
mIgnoreNextSessionLeave = TRUE;
getChannelInfo();
return;
}
else
{
notify = "VoiceChannelJoinFailed";
mRetries = DEFAULT_RETRIES_COUNT;
mIsRetrying = FALSE;
}
break;
case ERROR_UNKNOWN:
default:
break;
}
// notification
if (!notify.empty())
{
LLNotificationPtr notification = LLNotificationsUtil::add(notify, mNotifyArgs);
// echo to im window
gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage());
}
LLVoiceChannel::handleError(status);
}
void LLVoiceChannelGroup::setState(EState state)
{
switch(state)
{
case STATE_RINGING:
if ( !mIsRetrying )
{
gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs);
}
mState = state;
break;
default:
LLVoiceChannel::setState(state);
}
}
//
// LLVoiceChannelProximal
//
LLVoiceChannelProximal::LLVoiceChannelProximal() :
LLVoiceChannel(LLUUID::null, LLStringUtil::null)
{
activate();
}
BOOL LLVoiceChannelProximal::isActive()
{
return callStarted() && LLVoiceClient::getInstance()->inProximalChannel();
}
void LLVoiceChannelProximal::activate()
{
if (callStarted()) return;
LLVoiceChannel::activate();
if (callStarted())
{
// this implicitly puts you back in the spatial channel
LLVoiceClient::getInstance()->leaveNonSpatialChannel();
}
}
void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal)
{
if (!proximal)
{
return;
}
if (type < BEGIN_ERROR_STATUS)
{
handleStatusChange(type);
}
else
{
handleError(type);
}
}
void LLVoiceChannelProximal::handleStatusChange(EStatusType status)
{
// status updates
switch(status)
{
case STATUS_LEFT_CHANNEL:
// do not notify user when leaving proximal channel
return;
case STATUS_VOICE_DISABLED:
//skip showing "Voice not available at your current location" when agent voice is disabled (EXT-4749)
if(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking())
{
gIMMgr->addSystemMessage(LLUUID::null, "unavailable", mNotifyArgs);
}
return;
default:
break;
}
LLVoiceChannel::handleStatusChange(status);
}
void LLVoiceChannelProximal::handleError(EStatusType status)
{
std::string notify;
switch(status)
{
case ERROR_CHANNEL_LOCKED:
case ERROR_CHANNEL_FULL:
notify = "ProximalVoiceChannelFull";
break;
default:
break;
}
// notification
if (!notify.empty())
{
LLNotificationsUtil::add(notify, mNotifyArgs);
}
LLVoiceChannel::handleError(status);
}
void LLVoiceChannelProximal::deactivate()
{
if (callStarted())
{
setState(STATE_HUNG_UP);
}
}
//
// LLVoiceChannelP2P
//
LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) :
LLVoiceChannelGroup(session_id, session_name),
mOtherUserID(other_user_id),
mReceivedCall(FALSE)
{
// make sure URI reflects encoded version of other user's agent id
// *NOTE: in case of Avaline call generated SIP URL will be incorrect.
// But it will be overridden in LLVoiceChannelP2P::setSessionHandle() called when agent accepts call
setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id));
}
void LLVoiceChannelP2P::handleStatusChange(EStatusType type)
{
LL_INFOS("Voice") << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << LL_ENDL;
// status updates
switch(type)
{
case STATUS_LEFT_CHANNEL:
if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended)
{
// *TODO: use it to show DECLINE voice notification
if (mState == STATE_RINGING)
{
// other user declined call
LLNotificationsUtil::add("P2PCallDeclined", mNotifyArgs);
}
else
{
// other user hung up, so we didn't end the call
LLNotificationsUtil::add("VoiceChannelDisconnectedP2P", mNotifyArgs);
}
deactivate();
}
mIgnoreNextSessionLeave = FALSE;
return;
case STATUS_JOINING:
// because we join session we expect to process session leave event in the future. EXT-7371
// may be this should be done in the LLVoiceChannel::handleStatusChange.
mIgnoreNextSessionLeave = FALSE;
break;
default:
break;
}
LLVoiceChannel::handleStatusChange(type);
}
void LLVoiceChannelP2P::handleError(EStatusType type)
{
switch(type)
{
case ERROR_NOT_AVAILABLE:
LLNotificationsUtil::add("P2PCallNoAnswer", mNotifyArgs);
break;
default:
break;
}
LLVoiceChannel::handleError(type);
}
void LLVoiceChannelP2P::activate()
{
if (callStarted()) return;
LLVoiceChannel::activate();
if (callStarted())
{
// no session handle yet, we're starting the call
if (mSessionHandle.empty())
{
mReceivedCall = FALSE;
LLVoiceClient::getInstance()->callUser(mOtherUserID);
}
// otherwise answering the call
else
{
if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle))
{
mSessionHandle.clear();
handleError(ERROR_UNKNOWN);
return;
}
// using the session handle invalidates it. Clear it out here so we can't reuse it by accident.
mSessionHandle.clear();
}
//Default mic is ON on initiating/joining P2P calls
if (!LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
{
LLVoiceClient::getInstance()->inputUserControlState(true);
}
}
}
void LLVoiceChannelP2P::getChannelInfo()
{
// pretend we have everything we need, since P2P doesn't use channel info
if (sCurrentVoiceChannel == this)
{
setState(STATE_CALL_STARTED);
}
}
// receiving session from other user who initiated call
void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI)
{
BOOL needs_activate = FALSE;
if (callStarted())
{
// defer to lower agent id when already active
if (mOtherUserID < gAgent.getID())
{
// pretend we haven't started the call yet, so we can connect to this session instead
deactivate();
needs_activate = TRUE;
}
else
{
// we are active and have priority, invite the other user again
// under the assumption they will join this new session
mSessionHandle.clear();
LLVoiceClient::getInstance()->callUser(mOtherUserID);
return;
}
}
mSessionHandle = handle;
// The URI of a p2p session should always be the other end's SIP URI.
if(!inURI.empty())
{
setURI(inURI);
}
else
{
LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL;
// 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));
}
mReceivedCall = TRUE;
if (needs_activate)
{
activate();
}
}
void LLVoiceChannelP2P::setState(EState state)
{
LL_INFOS("Voice") << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << LL_ENDL;
if (mReceivedCall) // incoming call
{
// you only "answer" voice invites in p2p mode
// so provide a special purpose message here
if (mReceivedCall && state == STATE_RINGING)
{
gIMMgr->addSystemMessage(mSessionID, "answering", mNotifyArgs);
mState = state;
return;
}
}
LLVoiceChannel::setState(state);
}