diff --git a/indra/llmessage/aihttptimeoutpolicy.cpp b/indra/llmessage/aihttptimeoutpolicy.cpp index 785545376..93ba3b257 100644 --- a/indra/llmessage/aihttptimeoutpolicy.cpp +++ b/indra/llmessage/aihttptimeoutpolicy.cpp @@ -924,9 +924,7 @@ P(meshLODResponder); P(meshPhysicsShapeResponder); P(meshSkinInfoResponder); P(mimeDiscoveryResponder); -P(moderationModeResponder); -P(muteTextResponder); -P(muteVoiceResponder); +P(moderationResponder); P(navMeshRebakeResponder); P(navMeshResponder); P(navMeshStatusResponder); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 0d02be9a7..5af83cf18 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -416,6 +416,7 @@ set(viewer_SOURCE_FILES llselectmgr.cpp llsky.cpp llspatialpartition.cpp + llspeakers.cpp llsprite.cpp llstartup.cpp llstatusbar.cpp @@ -920,6 +921,7 @@ set(viewer_HEADER_FILES llsimplestat.h llsky.h llspatialpartition.h + llspeakers.h llsprite.h llstartup.h llstatusbar.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 69c256a17..15cc2aa54 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -15398,6 +15398,28 @@ This should be as low as possible, but too low may break functionality Value 0 + SpeakerParticipantDefaultOrder + + Comment + Order for displaying speakers in voice controls. 0 = alphabetical. 1 = recent. + Persist + 1 + Type + U32 + Value + 1 + + SpeakerParticipantRemoveDelay + + Comment + Timeout to remove participants who is not in channel before removed from list of active speakers (text/voice chat) + Persist + 1 + Type + F32 + Value + 10.0 + UseNewWalkRun Comment diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index c711f5fb5..6cededf8d 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -58,6 +58,7 @@ #include "llsdutil.h" #include "llsky.h" #include "llsmoothstep.h" +#include "llspeakers.h" #include "llstartup.h" #include "llstatusbar.h" #include "lltool.h" diff --git a/indra/newview/llfloateractivespeakers.cpp b/indra/newview/llfloateractivespeakers.cpp index abbf9be9f..ad66838d1 100644 --- a/indra/newview/llfloateractivespeakers.cpp +++ b/indra/newview/llfloateractivespeakers.cpp @@ -34,147 +34,25 @@ #include "llfloateractivespeakers.h" #include "llagent.h" -#include "llappviewer.h" #include "llavataractions.h" #include "llbutton.h" -#include "llimpanel.h" // LLFloaterIMPanel -#include "llimview.h" #include "llmutelist.h" #include "llscrolllistctrl.h" -#include "llsdutil.h" +#include "llspeakers.h" #include "lltextbox.h" #include "lluictrlfactory.h" #include "llviewerobjectlist.h" #include "llviewerwindow.h" #include "llvoavatar.h" -#include "llvoicechannel.h" #include "llworld.h" // [RLVa:KB] #include "rlvhandler.h" // [/RLVa:KB] -class AIHTTPTimeoutPolicy; -extern AIHTTPTimeoutPolicy muteVoiceResponder_timeout; -extern AIHTTPTimeoutPolicy muteTextResponder_timeout; -extern AIHTTPTimeoutPolicy moderationModeResponder_timeout; - using namespace LLOldEvents; -const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers const F32 RESORT_TIMEOUT = 5.f; // seconds of mouse inactivity before it's ok to sort regardless of mouse-in-view. -const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); -const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f); -const F32 TYPING_ANIMATION_FPS = 2.5f; - -LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerType type) : - mStatus(LLSpeaker::STATUS_TEXT_ONLY), - mLastSpokeTime(0.f), - mSpeechVolume(0.f), - mHasSpoken(FALSE), - mDotColor(LLColor4::white), - mID(id), - mTyping(FALSE), - mSortIndex(0), - mType(type), - mIsModerator(FALSE), - mModeratorMutedVoice(FALSE), - mModeratorMutedText(FALSE), - mNameRequested(false) -{ - // Make sure we also get the display name if SLIM or some other external - // voice client is used and not whatever is provided. - if ((name.empty() && type == SPEAKER_AGENT) || type == SPEAKER_EXTERNAL) - { - lookupName(); - } - else - { - mDisplayName = name; - mLegacyName = name; - } - mActivityTimer.reset(SPEAKER_TIMEOUT); -} - - -void LLSpeaker::lookupName() -{ - if(!mNameRequested) - { - mNameRequested = true; - LLAvatarNameCache::get(mID, boost::bind(&LLSpeaker::onNameCache, this, _2)); - } -} - -void LLSpeaker::onNameCache(const LLAvatarName& avatar_name) -{ - LLAvatarNameCache::getPNSName(avatar_name, mDisplayName); -// [RLVa:KB] - Checked: 2009-07-10 (RLVa-1.0.0g) | Added: RLVa-1.0.0g - // TODO-RLVa: this seems to get called per frame which is very likely an LL bug that will eventually get fixed - if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES)) - mDisplayName = RlvStrings::getAnonym(mDisplayName); -// [/RLVa:KB] - - // Also set the legacy name. We will need it to initiate a new - // IM session. - mLegacyName = LLCacheName::cleanFullName(avatar_name.getLegacyName()); -} - -LLSpeakerTextModerationEvent::LLSpeakerTextModerationEvent(LLSpeaker* source) -: LLEvent(source, "Speaker text moderation event") -{ -} - -LLSD LLSpeakerTextModerationEvent::getValue() -{ - return std::string("text"); -} - - -LLSpeakerVoiceModerationEvent::LLSpeakerVoiceModerationEvent(LLSpeaker* source) -: LLEvent(source, "Speaker voice moderation event") -{ -} - -LLSD LLSpeakerVoiceModerationEvent::getValue() -{ - return std::string("voice"); -} - -LLSpeakerListChangeEvent::LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id) -: LLEvent(source, "Speaker added/removed from speaker mgr"), - mSpeakerID(speaker_id) -{ -} - -LLSD LLSpeakerListChangeEvent::getValue() -{ - return mSpeakerID; -} - -// helper sort class -struct LLSortRecentSpeakers -{ - bool operator()(const LLPointer lhs, const LLPointer rhs) const; -}; - -bool LLSortRecentSpeakers::operator()(const LLPointer lhs, const LLPointer rhs) const -{ - // Sort first on status - if (lhs->mStatus != rhs->mStatus) - { - return (lhs->mStatus < rhs->mStatus); - } - - // and then on last speaking time - if(lhs->mLastSpokeTime != rhs->mLastSpokeTime) - { - return (lhs->mLastSpokeTime > rhs->mLastSpokeTime); - } - - // and finally (only if those are both equal), on name. - return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 ); -} // // LLFloaterActiveSpeakers @@ -610,10 +488,7 @@ void LLPanelActiveSpeakers::refreshSpeakers() && selected_id != gAgent.getID() && selected_speakerp.notNull() && selected_speakerp->mType != LLSpeaker::SPEAKER_EXTERNAL - // Ansariel: No, we don't want to mute Lindens with display names - //&& !LLMuteList::getInstance()->isLinden(selected_speakerp->mDisplayName)); - && !selected_speakerp->mLegacyName.empty() - && !LLMuteList::getInstance()->isLinden(selected_speakerp->mLegacyName)); + && !LLMuteList::getInstance()->isLinden(selected_id)); } mVolumeSlider->setValue(LLVoiceClient::getInstance()->getUserVolume(selected_id)); mVolumeSlider->setEnabled(LLVoiceClient::getInstance()->voiceEnabled() @@ -663,11 +538,6 @@ void LLPanelActiveSpeakers::refreshSpeakers() mSpeakerList->getScrollInterface()->setScrollPos(scroll_pos); } -void LLPanelActiveSpeakers::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) -{ - mSpeakerMgr->setSpeaker(id, name, status, type); -} - void LLPanelActiveSpeakers::setVoiceModerationCtrlMode( const BOOL& moderated_voice) { @@ -728,8 +598,8 @@ void LLPanelActiveSpeakers::onClickMuteVoiceCommit(LLUICtrl* ctrl, void* user_da name = speakerp->mDisplayName; - // muting voice means we're dealing with an agent - LLMute mute(speaker_id, name, LLMute::AGENT); + // muting voice means we're dealing with an agent or an external voice client which won't stay muted between sessions + LLMute mute(speaker_id, name, speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL ? LLMute::EXTERNAL : LLMute::AGENT); if (!is_muted) { @@ -790,66 +660,8 @@ void LLPanelActiveSpeakers::onModeratorMuteVoice(LLUICtrl* ctrl, void* user_data LLPanelActiveSpeakers* self = (LLPanelActiveSpeakers*)user_data; LLUICtrl* speakers_list = self->getChild("speakers_list"); if (!speakers_list || !gAgent.getRegion()) return; - - std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "mute update"; - data["session-id"] = self->mSpeakerMgr->getSessionID(); - data["params"] = LLSD::emptyMap(); - data["params"]["agent_id"] = speakers_list->getValue(); - data["params"]["mute_info"] = LLSD::emptyMap(); - // ctrl value represents ability to type, so invert - data["params"]["mute_info"]["voice"] = !ctrl->getValue(); - - class MuteVoiceResponder : public LLHTTPClient::ResponderIgnoreBody - { - public: - MuteVoiceResponder(const LLUUID& session_id) - { - mSessionID = session_id; - } - - /*virtual*/ void error(U32 status, const std::string& reason) - { - llwarns << status << ": " << reason << llendl; - - if ( gIMMgr ) - { - LLFloaterIMPanel* floaterp; - - floaterp = gIMMgr->findFloaterBySession(mSessionID); - - if ( floaterp ) - { - //403 == you're not a mod - //should be disabled if you're not a moderator - if ( 403 == status ) - { - floaterp->showSessionEventError( - "mute", - "not_a_mod_error"); - } - else - { - floaterp->showSessionEventError( - "mute", - "generic_request_error"); - } - } - } - } - - /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return muteVoiceResponder_timeout; } - /*virtual*/ char const* getName(void) const { return "MuteVoiceResponder"; } - - private: - LLUUID mSessionID; - }; - - LLHTTPClient::post( - url, - data, - new MuteVoiceResponder(self->mSpeakerMgr->getSessionID())); + if (LLIMSpeakerMgr* mgr = dynamic_cast(self->mSpeakerMgr)) + mgr->moderateVoiceParticipant(speakers_list->getValue(), ctrl->getValue()); } //static @@ -858,66 +670,8 @@ void LLPanelActiveSpeakers::onModeratorMuteText(LLUICtrl* ctrl, void* user_data) LLPanelActiveSpeakers* self = (LLPanelActiveSpeakers*)user_data; LLUICtrl* speakers_list = self->getChild("speakers_list"); if (!speakers_list || !gAgent.getRegion()) return; - - std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "mute update"; - data["session-id"] = self->mSpeakerMgr->getSessionID(); - data["params"] = LLSD::emptyMap(); - data["params"]["agent_id"] = speakers_list->getValue(); - data["params"]["mute_info"] = LLSD::emptyMap(); - // ctrl value represents ability to type, so invert - data["params"]["mute_info"]["text"] = !ctrl->getValue(); - - class MuteTextResponder : public LLHTTPClient::ResponderIgnoreBody - { - public: - MuteTextResponder(const LLUUID& session_id) - { - mSessionID = session_id; - } - - /*virtual*/ void error(U32 status, const std::string& reason) - { - llwarns << status << ": " << reason << llendl; - - if ( gIMMgr ) - { - LLFloaterIMPanel* floaterp; - - floaterp = gIMMgr->findFloaterBySession(mSessionID); - - if ( floaterp ) - { - //403 == you're not a mod - //should be disabled if you're not a moderator - if ( 403 == status ) - { - floaterp->showSessionEventError( - "mute", - "not_a_mod_error"); - } - else - { - floaterp->showSessionEventError( - "mute", - "generic_request_error"); - } - } - } - } - - /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return muteTextResponder_timeout; } - /*virtual*/ char const* getName(void) const { return "MuteTextResponder"; } - - private: - LLUUID mSessionID; - }; - - LLHTTPClient::post( - url, - data, - new MuteTextResponder(self->mSpeakerMgr->getSessionID())); + if (LLIMSpeakerMgr* mgr = dynamic_cast(self->mSpeakerMgr)) + mgr->toggleAllowTextChat(speakers_list->getValue()); } //static @@ -925,533 +679,17 @@ void LLPanelActiveSpeakers::onChangeModerationMode(LLUICtrl* ctrl, void* user_da { LLPanelActiveSpeakers* self = (LLPanelActiveSpeakers*)user_data; if (!gAgent.getRegion()) return; - - std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "session update"; - data["session-id"] = self->mSpeakerMgr->getSessionID(); - data["params"] = LLSD::emptyMap(); - - data["params"]["update_info"] = LLSD::emptyMap(); - - data["params"]["update_info"]["moderated_mode"] = LLSD::emptyMap(); - if (ctrl->getValue().asString() == "unmoderated") + // Singu Note: moderateVoiceAllParticipants ends up flipping the boolean passed to it before the actual post + if (LLIMSpeakerMgr* speaker_manager = dynamic_cast(self->mSpeakerMgr)) { - data["params"]["update_info"]["moderated_mode"]["voice"] = false; - } - else if (ctrl->getValue().asString() == "moderated") - { - data["params"]["update_info"]["moderated_mode"]["voice"] = true; - } - - struct ModerationModeResponder : public LLHTTPClient::ResponderIgnoreBody - { - /*virtual*/ void error(U32 status, const std::string& reason) + if (ctrl->getValue().asString() == "unmoderated") { - llwarns << status << ": " << reason << llendl; + speaker_manager->moderateVoiceAllParticipants(true); } - /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return moderationModeResponder_timeout; } - /*virtual*/ char const* getName(void) const { return "ModerationModeResponder"; } - }; - - LLHTTPClient::post(url, data, new ModerationModeResponder()); -} - -// -// LLSpeakerMgr -// - -LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : - mVoiceChannel(channelp) -{ -} - -LLSpeakerMgr::~LLSpeakerMgr() -{ -} - -LLPointer LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) -{ - if (id.isNull()) return NULL; - - LLPointer speakerp; - if (mSpeakers.find(id) == mSpeakers.end()) - { - speakerp = new LLSpeaker(id, name, type); - speakerp->mStatus = status; - mSpeakers.insert(std::make_pair(speakerp->mID, speakerp)); - mSpeakersSorted.push_back(speakerp); - fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "add"); - } - else - { - speakerp = findSpeaker(id); - if (speakerp.notNull()) + else if (ctrl->getValue().asString() == "moderated") { - // keep highest priority status (lowest value) instead of overriding current value - speakerp->mStatus = llmin(speakerp->mStatus, status); - speakerp->mActivityTimer.reset(SPEAKER_TIMEOUT); - // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id - // we need to override speakers that we think are objects when we find out they are really - // residents - if (type == LLSpeaker::SPEAKER_AGENT) - { - speakerp->mType = LLSpeaker::SPEAKER_AGENT; - speakerp->lookupName(); - } - } - } - - return speakerp; -} - -void LLSpeakerMgr::update(BOOL resort_ok) -{ - if (!LLVoiceClient::instanceExists()) - { - return; - } - - LLColor4 speaking_color = gSavedSettings.getColor4("SpeakingColor"); - LLColor4 overdriven_color = gSavedSettings.getColor4("OverdrivenColor"); - - if(resort_ok) // only allow list changes when user is not interacting with it - { - updateSpeakerList(); - } - - // update status of all current speakers - BOOL voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end();) - { - LLUUID speaker_id = speaker_it->first; - LLSpeaker* speakerp = speaker_it->second; - - speaker_it++; - - if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id)) - { - speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id); - BOOL moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id); - if (moderator_muted_voice != speakerp->mModeratorMutedVoice) - { - speakerp->mModeratorMutedVoice = moderator_muted_voice; - speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp)); - } - - if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) - { - speakerp->mStatus = LLSpeaker::STATUS_MUTED; - } - else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id)) - { - // reset inactivity expiration - if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) - { - speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); - speakerp->mHasSpoken = TRUE; - } - speakerp->mStatus = LLSpeaker::STATUS_SPEAKING; - // interpolate between active color and full speaking color based on power of speech output - speakerp->mDotColor = speaking_color; - if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL) - { - speakerp->mDotColor = overdriven_color; - } - } - else - { - speakerp->mSpeechVolume = 0.f; - speakerp->mDotColor = ACTIVE_COLOR; - - if (speakerp->mHasSpoken) - { - // have spoken once, not currently speaking - speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN; - } - else - { - // default state for being in voice channel - speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE; - } - } - } - // speaker no longer registered in voice channel, demote to text only - else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL) - { - if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) - { - // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - } - else - { - speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY; - speakerp->mSpeechVolume = 0.f; - speakerp->mDotColor = ACTIVE_COLOR; - } - } - } - - if(resort_ok) // only allow list changes when user is not interacting with it - { - // sort by status then time last spoken - std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers()); - } - - // for recent speakers who are not currently speaking, show "recent" color dot for most recent - // fading to "active" color - - S32 recent_speaker_count = 0; - S32 sort_index = 0; - speaker_list_t::iterator sorted_speaker_it; - for(sorted_speaker_it = mSpeakersSorted.begin(); - sorted_speaker_it != mSpeakersSorted.end(); ) - { - LLPointer speakerp = *sorted_speaker_it; - - // color code recent speakers who are not currently speaking - if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN) - { - speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f)); - recent_speaker_count++; - } - - // stuff sort ordinal into speaker so the ui can sort by this value - speakerp->mSortIndex = sort_index++; - - // remove speakers that have been gone too long - if (speakerp->mStatus == LLSpeaker::STATUS_NOT_IN_CHANNEL && speakerp->mActivityTimer.hasExpired()) - { - fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "remove"); - - mSpeakers.erase(speakerp->mID); - sorted_speaker_it = mSpeakersSorted.erase(sorted_speaker_it); - } - else - { - ++sorted_speaker_it; + speaker_manager->moderateVoiceAllParticipants(false); } } } -void LLSpeakerMgr::updateSpeakerList() -{ - // are we bound to the currently active voice channel? - if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) - { - std::set participants; - LLVoiceClient::getInstance()->getParticipantList(participants); - // add new participants to our list of known speakers - for (std::set::iterator participant_it = participants.begin(); - participant_it != participants.end(); - ++participant_it) - { - setSpeaker(*participant_it, - LLVoiceClient::getInstance()->getDisplayName(*participant_it), - LLSpeaker::STATUS_VOICE_ACTIVE, - (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); - - - } - } -} - -const LLPointer LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id) -{ - //In some conditions map causes crash if it is empty(Windows only), adding check (EK) - if (mSpeakers.size() == 0) - return NULL; - speaker_map_t::iterator found_it = mSpeakers.find(speaker_id); - if (found_it == mSpeakers.end()) - { - return NULL; - } - return found_it->second; -} - -void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, BOOL include_text) -{ - speaker_list->clear(); - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) - { - LLPointer speakerp = speaker_it->second; - // what about text only muted or inactive? - if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY) - { - speaker_list->push_back(speakerp); - } - } -} - -const LLUUID LLSpeakerMgr::getSessionID() -{ - return mVoiceChannel->getSessionID(); -} - - -void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, BOOL typing) -{ - LLPointer speakerp = findSpeaker(speaker_id); - if (speakerp.notNull()) - { - speakerp->mTyping = typing; - } -} - -// speaker has chatted via either text or voice -void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) -{ - LLPointer speakerp = findSpeaker(speaker_id); - if (speakerp.notNull()) - { - speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); - speakerp->mHasSpoken = TRUE; - } -} - -BOOL LLSpeakerMgr::isVoiceActive() -{ - // mVoiceChannel = NULL means current voice channel, whatever it is - return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); -} - - -// -// LLIMSpeakerMgr -// -LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel) -{ -} - -void LLIMSpeakerMgr::updateSpeakerList() -{ - // don't do normal updates which are pulled from voice channel - // rely on user list reported by sim - - // We need to do this to allow PSTN callers into group chats to show in the list. - LLSpeakerMgr::updateSpeakerList(); - - return; -} - -void LLIMSpeakerMgr::setSpeakers(const LLSD& speakers) -{ - if ( !speakers.isMap() ) return; - - if ( speakers.has("agent_info") && speakers["agent_info"].isMap() ) - { - LLSD::map_const_iterator speaker_it; - for(speaker_it = speakers["agent_info"].beginMap(); - speaker_it != speakers["agent_info"].endMap(); - ++speaker_it) - { - LLUUID agent_id(speaker_it->first); - - LLPointer speakerp = setSpeaker( - agent_id, - LLStringUtil::null, - LLSpeaker::STATUS_TEXT_ONLY); - - if ( speaker_it->second.isMap() ) - { - speakerp->mIsModerator = speaker_it->second["is_moderator"]; - speakerp->mModeratorMutedText = - speaker_it->second["mutes"]["text"]; - } - } - } - else if ( speakers.has("agents" ) && speakers["agents"].isArray() ) - { - //older, more decprecated way. Need here for - //using older version of servers - LLSD::array_const_iterator speaker_it; - for(speaker_it = speakers["agents"].beginArray(); - speaker_it != speakers["agents"].endArray(); - ++speaker_it) - { - const LLUUID agent_id = (*speaker_it).asUUID(); - - LLPointer speakerp = setSpeaker( - agent_id, - LLStringUtil::null, - LLSpeaker::STATUS_TEXT_ONLY); - } - } -} - -void LLIMSpeakerMgr::updateSpeakers(const LLSD& update) -{ - if ( !update.isMap() ) return; - - if ( update.has("agent_updates") && update["agent_updates"].isMap() ) - { - LLSD::map_const_iterator update_it; - for( - update_it = update["agent_updates"].beginMap(); - update_it != update["agent_updates"].endMap(); - ++update_it) - { - LLUUID agent_id(update_it->first); - LLPointer speakerp = findSpeaker(agent_id); - - LLSD agent_data = update_it->second; - - if (agent_data.isMap() && agent_data.has("transition")) - { - if (agent_data["transition"].asString() == "LEAVE" && speakerp.notNull()) - { - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - speakerp->mDotColor = INACTIVE_COLOR; - speakerp->mActivityTimer.reset(SPEAKER_TIMEOUT); - } - else if (agent_data["transition"].asString() == "ENTER") - { - // add or update speaker - speakerp = setSpeaker(agent_id); - } - else - { - llwarns << "bad membership list update " << ll_print_sd(agent_data["transition"]) << llendl; - } - } - - if (speakerp.isNull()) continue; - - // should have a valid speaker from this point on - if (agent_data.isMap() && agent_data.has("info")) - { - LLSD agent_info = agent_data["info"]; - - if (agent_info.has("is_moderator")) - { - speakerp->mIsModerator = agent_info["is_moderator"]; - } - - if (agent_info.has("mutes")) - { - speakerp->mModeratorMutedText = agent_info["mutes"]["text"]; - } - } - } - } - else if ( update.has("updates") && update["updates"].isMap() ) - { - LLSD::map_const_iterator update_it; - for ( - update_it = update["updates"].beginMap(); - update_it != update["updates"].endMap(); - ++update_it) - { - LLUUID agent_id(update_it->first); - LLPointer speakerp = findSpeaker(agent_id); - - std::string agent_transition = update_it->second.asString(); - if (agent_transition == "LEAVE" && speakerp.notNull()) - { - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - speakerp->mDotColor = INACTIVE_COLOR; - speakerp->mActivityTimer.reset(SPEAKER_TIMEOUT); - } - else if ( agent_transition == "ENTER") - { - // add or update speaker - speakerp = setSpeaker(agent_id); - } - else - { - llwarns << "bad membership list update " - << agent_transition << llendl; - } - } - } -} - - -// -// LLActiveSpeakerMgr -// - -LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL) -{ -} - -void LLActiveSpeakerMgr::updateSpeakerList() -{ - // point to whatever the current voice channel is - mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); - - // always populate from active voice channel - if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) - { - fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear"); - mSpeakers.clear(); - mSpeakersSorted.clear(); - mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); - } - LLSpeakerMgr::updateSpeakerList(); - - // clean up text only speakers - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) - { - LLUUID speaker_id = speaker_it->first; - LLSpeaker* speakerp = speaker_it->second; - if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) - { - // automatically flag text only speakers for removal - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - } - } - -} - - - -// -// LLLocalSpeakerMgr -// - -LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance()) -{ -} - -LLLocalSpeakerMgr::~LLLocalSpeakerMgr () -{ -} - -void LLLocalSpeakerMgr::updateSpeakerList() -{ - // pull speakers from voice channel - LLSpeakerMgr::updateSpeakerList(); - - if (gDisconnected)//the world is cleared. - { - return ; - } - - // pick up non-voice speakers in chat range - std::vector avatar_ids; - std::vector positions; - LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); - for(U32 i=0; ifirst; - LLSpeaker* speakerp = speaker_it->second; - if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) - { - LLVOAvatar* avatarp = gObjectList.findAvatar(speaker_id); - if (!avatarp || dist_vec(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS) - { - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - speakerp->mDotColor = INACTIVE_COLOR; - speakerp->mActivityTimer.reset(SPEAKER_TIMEOUT); - } - } - } -} diff --git a/indra/newview/llfloateractivespeakers.h b/indra/newview/llfloateractivespeakers.h index 82f6704cb..8fb66ee70 100644 --- a/indra/newview/llfloateractivespeakers.h +++ b/indra/newview/llfloateractivespeakers.h @@ -32,154 +32,16 @@ #ifndef LL_LLFLOATERACTIVESPEAKERS_H #define LL_LLFLOATERACTIVESPEAKERS_H -#include "llavatarnamecache.h" #include "llfloater.h" -#include "llmemory.h" #include "llvoiceclient.h" #include "llframetimer.h" #include "llevent.h" -#include -#include - class LLScrollListCtrl; class LLButton; class LLPanelActiveSpeakers; class LLSpeakerMgr; -class LLVoiceChannel; -class LLSlider; class LLTextBox; -class LLCheckBoxCtrl; - - -// data for a given participant in a voice channel -class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider, public boost::signals2::trackable -{ -public: - typedef enum e_speaker_type - { - SPEAKER_AGENT, - SPEAKER_OBJECT, - SPEAKER_EXTERNAL // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group) - } ESpeakerType; - - typedef enum e_speaker_status - { - STATUS_SPEAKING, - STATUS_HAS_SPOKEN, - STATUS_VOICE_ACTIVE, - STATUS_TEXT_ONLY, - STATUS_NOT_IN_CHANNEL, - STATUS_MUTED - } ESpeakerStatus; - - - LLSpeaker(const LLUUID& id, const std::string& name = LLStringUtil::null, const ESpeakerType type = SPEAKER_AGENT); - ~LLSpeaker() {}; - void lookupName(); - void onNameCache(const LLAvatarName& avatar_name); - - ESpeakerStatus mStatus; // current activity status in speech group - F32 mLastSpokeTime; // timestamp when this speaker last spoke - F32 mSpeechVolume; // current speech amplitude (timea average rms amplitude?) - std::string mDisplayName; // cache user name for this speaker - LLFrameTimer mActivityTimer; // time out speakers when they are not part of current voice channel - BOOL mHasSpoken; // has this speaker said anything this session? - LLColor4 mDotColor; - LLUUID mID; - BOOL mTyping; - S32 mSortIndex; - ESpeakerType mType; - BOOL mIsModerator; - BOOL mModeratorMutedVoice; - BOOL mModeratorMutedText; - std::string mLegacyName; - bool mNameRequested; -}; - -class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerTextModerationEvent(LLSpeaker* source); - /*virtual*/ LLSD getValue(); -}; - -class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerVoiceModerationEvent(LLSpeaker* source); - /*virtual*/ LLSD getValue(); -}; - -class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id); - /*virtual*/ LLSD getValue(); - -private: - const LLUUID& mSpeakerID; -}; - -class LLSpeakerMgr : public LLOldEvents::LLObservable -{ -public: - LLSpeakerMgr(LLVoiceChannel* channelp); - virtual ~LLSpeakerMgr(); - - const LLPointer findSpeaker(const LLUUID& avatar_id); - void update(BOOL resort_ok); - void setSpeakerTyping(const LLUUID& speaker_id, BOOL typing); - void speakerChatted(const LLUUID& speaker_id); - LLPointer setSpeaker(const LLUUID& id, - const std::string& name = LLStringUtil::null, - LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY, - LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT); - - BOOL isVoiceActive(); - - typedef std::vector > speaker_list_t; - void getSpeakerList(speaker_list_t* speaker_list, BOOL include_text); - const LLUUID getSessionID(); - -protected: - virtual void updateSpeakerList(); - - typedef std::map > speaker_map_t; - speaker_map_t mSpeakers; - - speaker_list_t mSpeakersSorted; - LLFrameTimer mSpeechTimer; - LLVoiceChannel* mVoiceChannel; -}; - -class LLIMSpeakerMgr : public LLSpeakerMgr -{ -public: - LLIMSpeakerMgr(LLVoiceChannel* channel); - - void updateSpeakers(const LLSD& update); - void setSpeakers(const LLSD& speakers); -protected: - virtual void updateSpeakerList(); -}; - -class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton -{ -public: - LLActiveSpeakerMgr(); -protected: - virtual void updateSpeakerList(); -}; - -class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton -{ -public: - LLLocalSpeakerMgr(); - ~LLLocalSpeakerMgr (); -protected: - virtual void updateSpeakerList(); -}; class LLFloaterActiveSpeakers : @@ -217,11 +79,6 @@ public: void handleSpeakerSelect(); void refreshSpeakers(); - - void setSpeaker(const LLUUID& id, - const std::string& name = LLStringUtil::null, - LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY, - LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT); void setVoiceModerationCtrlMode(const BOOL& moderated_voice); diff --git a/indra/newview/llfloaterchat.cpp b/indra/newview/llfloaterchat.cpp index bf92422a1..89aa5dc51 100644 --- a/indra/newview/llfloaterchat.cpp +++ b/indra/newview/llfloaterchat.cpp @@ -58,6 +58,7 @@ #include "llfloaterscriptdebug.h" #include "lllogchat.h" #include "llmutelist.h" +#include "llspeakers.h" #include "llstylemap.h" #include "lluictrlfactory.h" #include "llviewermessage.h" @@ -324,7 +325,7 @@ void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file) // add objects as transient speakers that can be muted if (chat.mSourceType == CHAT_SOURCE_OBJECT) { - chat_floater->mPanel->setSpeaker(chat.mFromID, chat.mFromName, LLSpeaker::STATUS_NOT_IN_CHANNEL, LLSpeaker::SPEAKER_OBJECT); + LLLocalSpeakerMgr::getInstance()->setSpeaker(chat.mFromID, chat.mFromName, LLSpeaker::STATUS_NOT_IN_CHANNEL, LLSpeaker::SPEAKER_OBJECT); } // start tab flashing on incoming text from other users (ignoring system text, etc) diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 25d61b5f2..94d3bee71 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -52,7 +52,6 @@ #include "llgroupactions.h" #include "llfloateractivespeakers.h" #include "llfloaterchat.h" -#include "llfloatergroupinfo.h" #include "llimview.h" #include "llinventory.h" #include "llinventoryfunctions.h" @@ -62,6 +61,7 @@ #include "lllineeditor.h" #include "llnotify.h" #include "llresmgr.h" +#include "llspeakers.h" #include "lltrans.h" #include "lltabcontainer.h" #include "llviewertexteditor.h" @@ -1478,7 +1478,7 @@ void LLFloaterIMPanel::processSessionUpdate(const LLSD& session_update) //update the speakers dropdown too - mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); + mSpeakerPanel->setVoiceModerationCtrlMode(session_update); } } diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h index edd219d5b..907993388 100644 --- a/indra/newview/llimpanel.h +++ b/indra/newview/llimpanel.h @@ -131,6 +131,7 @@ public: void processSessionUpdate(const LLSD& update); void setSpeakers(const LLSD& speaker_list); LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } + LLIMSpeakerMgr* getSpeakerManager() const { return mSpeakers; } // Singu TODO: LLIMModel::getSpeakerManager EInstantMessage getDialogType() const { return mDialog; } void requestAutoConnect(); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index e256ac739..06ac47b5b 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -54,9 +54,9 @@ #include "llhttpnode.h" #include "llimpanel.h" #include "llsdserialize.h" +#include "llspeakers.h" #include "lltabcontainer.h" #include "llmutelist.h" -#include "llresizehandle.h" #include "llviewermenu.h" #include "llviewermessage.h" #include "llviewerwindow.h" @@ -1552,11 +1552,17 @@ public: const LLSD& context, const LLSD& input) const { - LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(input["body"]["session_id"].asUUID()); + LLUUID session_id = input["body"]["session_id"].asUUID(); + LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(session_id); if (floaterp) { floaterp->processSessionUpdate(input["body"]["info"]); } + LLIMSpeakerMgr* im_mgr = floaterp ? floaterp->getSpeakerManager() : NULL; //LLIMModel::getInstance()->getSpeakerManager(session_id); + if (im_mgr) + { + im_mgr->processSessionUpdate(input["body"]["info"]); + } } }; diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index 539b1923c..77a08d183 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -129,7 +129,7 @@ void LLPanelVoiceDeviceSettings::draw() } else { - color = gSavedSettings.getColor4("FocusBackgroundColor"); + color = LLUI::sColorsGroup->getColor("FocusBackgroundColor"); } LLRect color_rect = bar_view->getRect(); diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp new file mode 100644 index 000000000..880a6c8f7 --- /dev/null +++ b/indra/newview/llspeakers.cpp @@ -0,0 +1,960 @@ +/** + * @file llspeakers.cpp + * @brief Management interface for muting and controlling volume of residents currently speaking + * + * $LicenseInfo:firstyear=2005&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 "llspeakers.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llimpanel.h" // For LLFloaterIMPanel +#include "llimview.h" +#include "llsdutil.h" +#include "llui.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "llvoicechannel.h" +#include "llworld.h" + +#include "rlvhandler.h" + +extern AIHTTPTimeoutPolicy moderationResponder_timeout; + +const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); +const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f); + +LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerType type) : + mStatus(LLSpeaker::STATUS_TEXT_ONLY), + mLastSpokeTime(0.f), + mSpeechVolume(0.f), + mHasSpoken(FALSE), + mHasLeftCurrentCall(FALSE), + mDotColor(LLColor4::white), + mID(id), + mTyping(FALSE), + mSortIndex(0), + mType(type), + mIsModerator(FALSE), + mModeratorMutedVoice(FALSE), + mModeratorMutedText(FALSE) +{ + // Make sure we also get the display name if SLIM or some other external voice client is used and not whatever is provided. + if ((name.empty() && type == SPEAKER_AGENT) || type == SPEAKER_EXTERNAL) + { + lookupName(); + } + else + { + mDisplayName = name; + } +} + + +void LLSpeaker::lookupName() +{ + if (mDisplayName.empty()) + { +// [RLVa:KB] - Checked: 2009-07-10 (RLVa-1.0.0g) | Added: RLVa-1.0.0g + if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES) && gAgentID != mID) + mDisplayName = RlvStrings::getAnonym(mDisplayName); + else +// [/RLVa:KB] + LLAvatarNameCache::get(mID, boost::bind(&LLSpeaker::onNameCache, this, _2)); + } +} + +void LLSpeaker::onNameCache(const LLAvatarName& full_name) +{ + LLAvatarNameCache::getPNSName(full_name, mDisplayName); +} + +bool LLSpeaker::isInVoiceChannel() +{ + return mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || mStatus == LLSpeaker::STATUS_MUTED; +} + +LLSpeakerUpdateModeratorEvent::LLSpeakerUpdateModeratorEvent(LLSpeaker* source) +: LLEvent(source, "Speaker add moderator event"), + mSpeakerID (source->mID), + mIsModerator (source->mIsModerator) +{ +} + +LLSD LLSpeakerUpdateModeratorEvent::getValue() +{ + LLSD ret; + ret["id"] = mSpeakerID; + ret["is_moderator"] = mIsModerator; + return ret; +} + + +LLSpeakerTextModerationEvent::LLSpeakerTextModerationEvent(LLSpeaker* source) +: LLEvent(source, "Speaker text moderation event") +{ +} + +LLSD LLSpeakerTextModerationEvent::getValue() +{ + return std::string("text"); +} + + +LLSpeakerVoiceModerationEvent::LLSpeakerVoiceModerationEvent(LLSpeaker* source) +: LLEvent(source, "Speaker voice moderation event") +{ +} + +LLSD LLSpeakerVoiceModerationEvent::getValue() +{ + return std::string("voice"); +} + +LLSpeakerListChangeEvent::LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id) +: LLEvent(source, "Speaker added/removed from speaker mgr"), + mSpeakerID(speaker_id) +{ +} + +LLSD LLSpeakerListChangeEvent::getValue() +{ + return mSpeakerID; +} + +// helper sort class +struct LLSortRecentSpeakers +{ + bool operator()(const LLPointer lhs, const LLPointer rhs) const; +}; + +bool LLSortRecentSpeakers::operator()(const LLPointer lhs, const LLPointer rhs) const +{ + // Sort first on status + if (lhs->mStatus != rhs->mStatus) + { + return (lhs->mStatus < rhs->mStatus); + } + + // and then on last speaking time + if(lhs->mLastSpokeTime != rhs->mLastSpokeTime) + { + return (lhs->mLastSpokeTime > rhs->mLastSpokeTime); + } + + // and finally (only if those are both equal), on name. + return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 ); +} + +LLSpeakerActionTimer::LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id) +: LLEventTimer(action_period) +, mActionCallback(action_cb) +, mSpeakerId(speaker_id) +{ +} + +BOOL LLSpeakerActionTimer::tick() +{ + if (mActionCallback) + { + return (BOOL)mActionCallback(mSpeakerId); + } + return TRUE; +} + +void LLSpeakerActionTimer::unset() +{ + mActionCallback = 0; +} + +LLSpeakersDelayActionsStorage::LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay) +: mActionCallback(action_cb) +, mActionDelay(action_delay) +{ +} + +LLSpeakersDelayActionsStorage::~LLSpeakersDelayActionsStorage() +{ + removeAllTimers(); +} + +void LLSpeakersDelayActionsStorage::setActionTimer(const LLUUID& speaker_id) +{ + bool not_found = true; + if (mActionTimersMap.size() > 0) + { + not_found = mActionTimersMap.find(speaker_id) == mActionTimersMap.end(); + } + + // If there is already a started timer for the passed UUID don't do anything. + if (not_found) + { + // Starting a timer to remove an participant after delay is completed + mActionTimersMap.insert(LLSpeakerActionTimer::action_value_t(speaker_id, + new LLSpeakerActionTimer( + boost::bind(&LLSpeakersDelayActionsStorage::onTimerActionCallback, this, _1), + mActionDelay, speaker_id))); + } +} + +void LLSpeakersDelayActionsStorage::unsetActionTimer(const LLUUID& speaker_id) +{ + if (mActionTimersMap.size() == 0) return; + + LLSpeakerActionTimer::action_timer_iter_t it_speaker = mActionTimersMap.find(speaker_id); + + if (it_speaker != mActionTimersMap.end()) + { + it_speaker->second->unset(); + mActionTimersMap.erase(it_speaker); + } +} + +void LLSpeakersDelayActionsStorage::removeAllTimers() +{ + LLSpeakerActionTimer::action_timer_iter_t iter = mActionTimersMap.begin(); + for (; iter != mActionTimersMap.end(); ++iter) + { + delete iter->second; + } + mActionTimersMap.clear(); +} + +bool LLSpeakersDelayActionsStorage::onTimerActionCallback(const LLUUID& speaker_id) +{ + unsetActionTimer(speaker_id); + + if (mActionCallback) + { + mActionCallback(speaker_id); + } + + return true; +} + + +// +// LLSpeakerMgr +// + +LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : + mVoiceChannel(channelp) +, mVoiceModerated(false) +, mModerateModeHandledFirstTime(false) +{ + static LLUICachedControl remove_delay ("SpeakerParticipantRemoveDelay", 10.0); + + mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLSpeakerMgr::removeSpeaker, this, _1), remove_delay); +} + +LLSpeakerMgr::~LLSpeakerMgr() +{ + delete mSpeakerDelayRemover; +} + +LLPointer LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) +{ + if (id.isNull()) return NULL; + + LLPointer speakerp; + if (mSpeakers.find(id) == mSpeakers.end()) + { + speakerp = new LLSpeaker(id, name, type); + speakerp->mStatus = status; + mSpeakers.insert(std::make_pair(speakerp->mID, speakerp)); + mSpeakersSorted.push_back(speakerp); + LL_DEBUGS("Speakers") << "Added speaker " << id << llendl; + fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "add"); + } + else + { + speakerp = findSpeaker(id); + if (speakerp.notNull()) + { + // keep highest priority status (lowest value) instead of overriding current value + speakerp->mStatus = llmin(speakerp->mStatus, status); + // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id + // we need to override speakers that we think are objects when we find out they are really + // residents + if (type == LLSpeaker::SPEAKER_AGENT) + { + speakerp->mType = LLSpeaker::SPEAKER_AGENT; + speakerp->lookupName(); + } + } + else + { + LL_WARNS("Speakers") << "Speaker " << id << " not found" << llendl; + } + } + + mSpeakerDelayRemover->unsetActionTimer(speakerp->mID); + return speakerp; +} + +// *TODO: Once way to request the current voice channel moderation mode is implemented +// this method with related code should be removed. +/* + Initializes "moderate_mode" of voice session on first join. + + This is WORKAROUND because a way to request the current voice channel moderation mode exists + but is not implemented in viewer yet. See EXT-6937. +*/ +void LLSpeakerMgr::initVoiceModerateMode() +{ + if (!mModerateModeHandledFirstTime && (mVoiceChannel && mVoiceChannel->isActive())) + { + LLPointer speakerp; + + if (mSpeakers.find(gAgentID) != mSpeakers.end()) + { + speakerp = mSpeakers[gAgentID]; + } + + if (speakerp.notNull()) + { + mVoiceModerated = speakerp->mModeratorMutedVoice; + mModerateModeHandledFirstTime = true; + } + } +} + +void LLSpeakerMgr::update(BOOL resort_ok) +{ + if (!LLVoiceClient::instanceExists()) + { + return; + } + + LLColor4 speaking_color = gSavedSettings.getColor4("SpeakingColor"); + LLColor4 overdriven_color = gSavedSettings.getColor4("OverdrivenColor"); + + if(resort_ok) // only allow list changes when user is not interacting with it + { + updateSpeakerList(); + } + + // update status of all current speakers + BOOL voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end();) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + + speaker_it++; + + if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id)) + { + speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id); + BOOL moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id); + if (moderator_muted_voice != speakerp->mModeratorMutedVoice) + { + speakerp->mModeratorMutedVoice = moderator_muted_voice; + LL_DEBUGS("Speakers") << (speakerp->mModeratorMutedVoice? "Muted" : "Umuted") << " speaker " << speaker_id<< llendl; + speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp)); + } + + if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) + { + speakerp->mStatus = LLSpeaker::STATUS_MUTED; + } + else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id)) + { + // reset inactivity expiration + if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) + { + speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); + speakerp->mHasSpoken = TRUE; + } + speakerp->mStatus = LLSpeaker::STATUS_SPEAKING; + // interpolate between active color and full speaking color based on power of speech output + speakerp->mDotColor = speaking_color; + if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL) + { + speakerp->mDotColor = overdriven_color; + } + } + else + { + speakerp->mSpeechVolume = 0.f; + speakerp->mDotColor = ACTIVE_COLOR; + + if (speakerp->mHasSpoken) + { + // have spoken once, not currently speaking + speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN; + } + else + { + // default state for being in voice channel + speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE; + } + } + } + // speaker no longer registered in voice channel, demote to text only + else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL) + { + if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) + { + // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + } + else + { + speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY; + speakerp->mSpeechVolume = 0.f; + speakerp->mDotColor = ACTIVE_COLOR; + } + } + } + + if(resort_ok) // only allow list changes when user is not interacting with it + { + // sort by status then time last spoken + std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers()); + } + + // for recent speakers who are not currently speaking, show "recent" color dot for most recent + // fading to "active" color + + S32 recent_speaker_count = 0; + S32 sort_index = 0; + speaker_list_t::iterator sorted_speaker_it; + for(sorted_speaker_it = mSpeakersSorted.begin(); + sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it) + { + LLPointer speakerp = *sorted_speaker_it; + + // color code recent speakers who are not currently speaking + if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN) + { + speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f)); + recent_speaker_count++; + } + + // stuff sort ordinal into speaker so the ui can sort by this value + speakerp->mSortIndex = sort_index++; + } +} + +void LLSpeakerMgr::updateSpeakerList() +{ + // are we bound to the currently active voice channel? + if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) + { + std::set participants; + LLVoiceClient::getInstance()->getParticipantList(participants); + // add new participants to our list of known speakers + for (std::set::iterator participant_it = participants.begin(); + participant_it != participants.end(); + ++participant_it) + { + setSpeaker(*participant_it, + LLVoiceClient::getInstance()->getDisplayName(*participant_it), + LLSpeaker::STATUS_VOICE_ACTIVE, + (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); + + + } + } +} + +void LLSpeakerMgr::setSpeakerNotInChannel(LLSpeaker* speakerp) +{ + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + speakerp->mDotColor = INACTIVE_COLOR; + mSpeakerDelayRemover->setActionTimer(speakerp->mID); +} + +bool LLSpeakerMgr::removeSpeaker(const LLUUID& speaker_id) +{ + mSpeakers.erase(speaker_id); + + speaker_list_t::iterator sorted_speaker_it = mSpeakersSorted.begin(); + + for(; sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it) + { + if (speaker_id == (*sorted_speaker_it)->mID) + { + mSpeakersSorted.erase(sorted_speaker_it); + break; + } + } + + LL_DEBUGS("Speakers") << "Removed speaker " << speaker_id << llendl; + fireEvent(new LLSpeakerListChangeEvent(this, speaker_id), "remove"); + + update(TRUE); + + return false; +} + +LLPointer LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id) +{ + //In some conditions map causes crash if it is empty(Windows only), adding check (EK) + if (mSpeakers.size() == 0) + return NULL; + speaker_map_t::iterator found_it = mSpeakers.find(speaker_id); + if (found_it == mSpeakers.end()) + { + return NULL; + } + return found_it->second; +} + +void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, BOOL include_text) +{ + speaker_list->clear(); + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLPointer speakerp = speaker_it->second; + // what about text only muted or inactive? + if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY) + { + speaker_list->push_back(speakerp); + } + } +} + +const LLUUID LLSpeakerMgr::getSessionID() +{ + return mVoiceChannel->getSessionID(); +} + + +void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, BOOL typing) +{ + LLPointer speakerp = findSpeaker(speaker_id); + if (speakerp.notNull()) + { + speakerp->mTyping = typing; + } +} + +// speaker has chatted via either text or voice +void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) +{ + LLPointer speakerp = findSpeaker(speaker_id); + if (speakerp.notNull()) + { + speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); + speakerp->mHasSpoken = TRUE; + } +} + +BOOL LLSpeakerMgr::isVoiceActive() +{ + // mVoiceChannel = NULL means current voice channel, whatever it is + return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); +} + + +// +// LLIMSpeakerMgr +// +LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel) +{ +} + +void LLIMSpeakerMgr::updateSpeakerList() +{ + // don't do normal updates which are pulled from voice channel + // rely on user list reported by sim + + // We need to do this to allow PSTN callers into group chats to show in the list. + LLSpeakerMgr::updateSpeakerList(); + + return; +} + +void LLIMSpeakerMgr::setSpeakers(const LLSD& speakers) +{ + if ( !speakers.isMap() ) return; + + if ( speakers.has("agent_info") && speakers["agent_info"].isMap() ) + { + LLSD::map_const_iterator speaker_it; + for(speaker_it = speakers["agent_info"].beginMap(); + speaker_it != speakers["agent_info"].endMap(); + ++speaker_it) + { + LLUUID agent_id(speaker_it->first); + + LLPointer speakerp = setSpeaker( + agent_id, + LLStringUtil::null, + LLSpeaker::STATUS_TEXT_ONLY); + + if ( speaker_it->second.isMap() ) + { + BOOL is_moderator = speakerp->mIsModerator; + speakerp->mIsModerator = speaker_it->second["is_moderator"]; + speakerp->mModeratorMutedText = + speaker_it->second["mutes"]["text"]; + // Fire event only if moderator changed + if ( is_moderator != speakerp->mIsModerator ) + { + LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << llendl; + fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator"); + } + } + } + } + else if ( speakers.has("agents" ) && speakers["agents"].isArray() ) + { + //older, more decprecated way. Need here for + //using older version of servers + LLSD::array_const_iterator speaker_it; + for(speaker_it = speakers["agents"].beginArray(); + speaker_it != speakers["agents"].endArray(); + ++speaker_it) + { + const LLUUID agent_id = (*speaker_it).asUUID(); + + LLPointer speakerp = setSpeaker( + agent_id, + LLStringUtil::null, + LLSpeaker::STATUS_TEXT_ONLY); + } + } +} + +void LLIMSpeakerMgr::updateSpeakers(const LLSD& update) +{ + if ( !update.isMap() ) return; + + if ( update.has("agent_updates") && update["agent_updates"].isMap() ) + { + LLSD::map_const_iterator update_it; + for( + update_it = update["agent_updates"].beginMap(); + update_it != update["agent_updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLPointer speakerp = findSpeaker(agent_id); + + LLSD agent_data = update_it->second; + + if (agent_data.isMap() && agent_data.has("transition")) + { + if (agent_data["transition"].asString() == "LEAVE" && speakerp.notNull()) + { + setSpeakerNotInChannel(speakerp); + } + else if (agent_data["transition"].asString() == "ENTER") + { + // add or update speaker + speakerp = setSpeaker(agent_id); + } + else + { + llwarns << "bad membership list update " << ll_print_sd(agent_data["transition"]) << llendl; + } + } + + if (speakerp.isNull()) continue; + + // should have a valid speaker from this point on + if (agent_data.isMap() && agent_data.has("info")) + { + LLSD agent_info = agent_data["info"]; + + if (agent_info.has("is_moderator")) + { + BOOL is_moderator = speakerp->mIsModerator; + speakerp->mIsModerator = agent_info["is_moderator"]; + // Fire event only if moderator changed + if ( is_moderator != speakerp->mIsModerator ) + { + LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << llendl; + fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator"); + } + } + + if (agent_info.has("mutes")) + { + speakerp->mModeratorMutedText = agent_info["mutes"]["text"]; + } + } + } + } + else if ( update.has("updates") && update["updates"].isMap() ) + { + LLSD::map_const_iterator update_it; + for ( + update_it = update["updates"].beginMap(); + update_it != update["updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLPointer speakerp = findSpeaker(agent_id); + + std::string agent_transition = update_it->second.asString(); + if (agent_transition == "LEAVE" && speakerp.notNull()) + { + setSpeakerNotInChannel(speakerp); + } + else if ( agent_transition == "ENTER") + { + // add or update speaker + speakerp = setSpeaker(agent_id); + } + else + { + llwarns << "bad membership list update " + << agent_transition << llendl; + } + } + } +} + +class ModerationResponder : public LLHTTPClient::ResponderIgnoreBody +{ +public: + ModerationResponder(const LLUUID& session_id) + { + mSessionID = session_id; + } + + /*virtual*/ void error(U32 status, const std::string& reason) + { + llwarns << "ModerationResponder error [status:" << status << "]: " << reason << llendl; + + if ( gIMMgr ) + { + LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(mSessionID); + if (!floaterp) return; + + //403 == you're not a mod + //should be disabled if you're not a moderator + if ( 403 == status ) + { + floaterp->showSessionEventError( + "mute", + "not_a_mod_error"); + } + else + { + floaterp->showSessionEventError( + "mute", + "generic_request_error"); + } + } + } + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return moderationResponder_timeout; } + /*virtual*/ char const* getName(void) const { return "ModerationResponder"; } + +private: + LLUUID mSessionID; +}; + +void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id) +{ + LLPointer speakerp = findSpeaker(speaker_id); + if (!speakerp) return; + + std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "mute update"; + data["session-id"] = getSessionID(); + data["params"] = LLSD::emptyMap(); + data["params"]["agent_id"] = speaker_id; + data["params"]["mute_info"] = LLSD::emptyMap(); + //current value represents ability to type, so invert + data["params"]["mute_info"]["text"] = !speakerp->mModeratorMutedText; + + LLHTTPClient::post(url, data, new ModerationResponder(getSessionID())); +} + +void LLIMSpeakerMgr::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) +{ + LLPointer speakerp = findSpeaker(avatar_id); + if (!speakerp) return; + + // *NOTE: mantipov: probably this condition will be incorrect when avatar will be blocked for + // text chat via moderation (LLSpeaker::mModeratorMutedText == TRUE) + bool is_in_voice = speakerp->mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || speakerp->mStatus == LLSpeaker::STATUS_MUTED; + + // do not send voice moderation changes for avatars not in voice channel + if (!is_in_voice) return; + + std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "mute update"; + data["session-id"] = getSessionID(); + data["params"] = LLSD::emptyMap(); + data["params"]["agent_id"] = avatar_id; + data["params"]["mute_info"] = LLSD::emptyMap(); + data["params"]["mute_info"]["voice"] = !unmute; + + LLHTTPClient::post( + url, + data, + new ModerationResponder(getSessionID())); +} + +void LLIMSpeakerMgr::moderateVoiceAllParticipants( bool unmute_everyone ) +{ + if (mVoiceModerated == !unmute_everyone) + { + // session already in requested state. Just force participants which do not match it. + forceVoiceModeratedMode(mVoiceModerated); + } + else + { + // otherwise set moderated mode for a whole session. + moderateVoiceSession(getSessionID(), !unmute_everyone); + } +} + +void LLIMSpeakerMgr::processSessionUpdate(const LLSD& session_update) +{ + if (session_update.has("moderated_mode") && + session_update["moderated_mode"].has("voice")) + { + mVoiceModerated = session_update["moderated_mode"]["voice"]; + } +} + +void LLIMSpeakerMgr::moderateVoiceSession(const LLUUID& session_id, bool disallow_voice) +{ + std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "session update"; + data["session-id"] = session_id; + data["params"] = LLSD::emptyMap(); + + data["params"]["update_info"] = LLSD::emptyMap(); + + data["params"]["update_info"]["moderated_mode"] = LLSD::emptyMap(); + data["params"]["update_info"]["moderated_mode"]["voice"] = disallow_voice; + + LLHTTPClient::post(url, data, new ModerationResponder(session_id)); +} + +void LLIMSpeakerMgr::forceVoiceModeratedMode(bool should_be_muted) +{ + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + + // participant does not match requested state + if (should_be_muted != (bool)speakerp->mModeratorMutedVoice) + { + moderateVoiceParticipant(speaker_id, !should_be_muted); + } + } +} + +// +// LLActiveSpeakerMgr +// + +LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL) +{ +} + +void LLActiveSpeakerMgr::updateSpeakerList() +{ + // point to whatever the current voice channel is + mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); + + // always populate from active voice channel + if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) //MA: seems this is always false + { + LL_DEBUGS("Speakers") << "Removed all speakers" << llendl; + fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear"); + mSpeakers.clear(); + mSpeakersSorted.clear(); + mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); + mSpeakerDelayRemover->removeAllTimers(); + } + LLSpeakerMgr::updateSpeakerList(); + + // clean up text only speakers + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) + { + // automatically flag text only speakers for removal + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + } + } + +} + + + +// +// LLLocalSpeakerMgr +// + +LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance()) +{ +} + +LLLocalSpeakerMgr::~LLLocalSpeakerMgr () +{ +} + +void LLLocalSpeakerMgr::updateSpeakerList() +{ + // pull speakers from voice channel + LLSpeakerMgr::updateSpeakerList(); + + if (gDisconnected)//the world is cleared. + { + return ; + } + + // pick up non-voice speakers in chat range + uuid_vec_t avatar_ids; + std::vector positions; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); + for(U32 i=0; ifirst; + LLSpeaker* speakerp = speaker_it->second; + if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) + { + LLVOAvatar* avatarp = gObjectList.findAvatar(speaker_id); + if (!avatarp || dist_vec_squared(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS_SQUARED) + { + setSpeakerNotInChannel(speakerp); + } + } + } +} + diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h new file mode 100644 index 000000000..1d58d1b1d --- /dev/null +++ b/indra/newview/llspeakers.h @@ -0,0 +1,347 @@ +/** + * @file llspeakers.h + * @brief Management interface for muting and controlling volume of residents currently speaking + * + * $LicenseInfo:firstyear=2005&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$ + */ + +#ifndef LL_LLSPEAKERS_H +#define LL_LLSPEAKERS_H + +#include "llevent.h" +#include "lleventtimer.h" +#include "llhandle.h" + +class LLAvatarName; +class LLSpeakerMgr; +class LLVoiceChannel; + +// data for a given participant in a voice channel +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider, public boost::signals2::trackable +{ +public: + typedef enum e_speaker_type + { + SPEAKER_AGENT, + SPEAKER_OBJECT, + SPEAKER_EXTERNAL // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group) + } ESpeakerType; + + typedef enum e_speaker_status + { + STATUS_SPEAKING, + STATUS_HAS_SPOKEN, + STATUS_VOICE_ACTIVE, + STATUS_TEXT_ONLY, + STATUS_NOT_IN_CHANNEL, + STATUS_MUTED + } ESpeakerStatus; + + + LLSpeaker(const LLUUID& id, const std::string& name = LLStringUtil::null, const ESpeakerType type = SPEAKER_AGENT); + ~LLSpeaker() {}; + void lookupName(); + + void onNameCache(const LLAvatarName& full_name); + + bool isInVoiceChannel(); + + ESpeakerStatus mStatus; // current activity status in speech group + F32 mLastSpokeTime; // timestamp when this speaker last spoke + F32 mSpeechVolume; // current speech amplitude (timea average rms amplitude?) + std::string mDisplayName; // cache user name for this speaker + BOOL mHasSpoken; // has this speaker said anything this session? + BOOL mHasLeftCurrentCall; // has this speaker left the current voice call? + LLColor4 mDotColor; + LLUUID mID; + BOOL mTyping; + S32 mSortIndex; + ESpeakerType mType; + BOOL mIsModerator; + BOOL mModeratorMutedVoice; + BOOL mModeratorMutedText; +}; + +class LLSpeakerUpdateModeratorEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerUpdateModeratorEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +private: + const LLUUID& mSpeakerID; + BOOL mIsModerator; +}; + +class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerTextModerationEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +}; + +class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerVoiceModerationEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +}; + +class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id); + /*virtual*/ LLSD getValue(); + +private: + const LLUUID& mSpeakerID; +}; + +/** + * class LLSpeakerActionTimer + * + * Implements a timer that calls stored callback action for stored speaker after passed period. + * + * Action is called until callback returns "true". + * In this case the timer will be removed via LLEventTimer::updateClass(). + * Otherwise it should be deleted manually in place where it is used. + * If action callback is not set timer will tick only once and deleted. + */ +class LLSpeakerActionTimer : public LLEventTimer +{ +public: + typedef boost::function action_callback_t; + typedef std::map action_timers_map_t; + typedef action_timers_map_t::value_type action_value_t; + typedef action_timers_map_t::const_iterator action_timer_const_iter_t; + typedef action_timers_map_t::iterator action_timer_iter_t; + + /** + * Constructor. + * + * @param action_cb - callback which will be called each time after passed action period. + * @param action_period - time in seconds timer should tick. + * @param speaker_id - LLUUID of speaker which will be passed into action callback. + */ + LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id); + virtual ~LLSpeakerActionTimer() {}; + + /** + * Implements timer "tick". + * + * If action callback is not specified returns true. Instance will be deleted by LLEventTimer::updateClass(). + */ + virtual BOOL tick(); + + /** + * Clears the callback. + * + * Use this instead of deleteing this object. + * The next call to tick() will return true and that will destroy this object. + */ + void unset(); +private: + action_callback_t mActionCallback; + LLUUID mSpeakerId; +}; + +/** + * Represents a functionality to store actions for speakers with delay. + * Is based on LLSpeakerActionTimer. + */ +class LLSpeakersDelayActionsStorage +{ +public: + LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay); + ~LLSpeakersDelayActionsStorage(); + + /** + * Sets new LLSpeakerActionTimer with passed speaker UUID. + */ + void setActionTimer(const LLUUID& speaker_id); + + /** + * Removes stored LLSpeakerActionTimer for passed speaker UUID from internal map and optionally deletes it. + * + * @see onTimerActionCallback() + */ + void unsetActionTimer(const LLUUID& speaker_id); + + void removeAllTimers(); +private: + /** + * Callback of the each instance of LLSpeakerActionTimer. + * + * Unsets an appropriate timer instance and calls action callback for specified speacker_id. + * + * @see unsetActionTimer() + */ + bool onTimerActionCallback(const LLUUID& speaker_id); + + LLSpeakerActionTimer::action_timers_map_t mActionTimersMap; + LLSpeakerActionTimer::action_callback_t mActionCallback; + + /** + * Delay to call action callback for speakers after timer was set. + */ + F32 mActionDelay; + +}; + + +class LLSpeakerMgr : public LLOldEvents::LLObservable +{ + LOG_CLASS(LLSpeakerMgr); + +public: + LLSpeakerMgr(LLVoiceChannel* channelp); + virtual ~LLSpeakerMgr(); + + LLPointer findSpeaker(const LLUUID& avatar_id); + void update(BOOL resort_ok); + void setSpeakerTyping(const LLUUID& speaker_id, BOOL typing); + void speakerChatted(const LLUUID& speaker_id); + LLPointer setSpeaker(const LLUUID& id, + const std::string& name = LLStringUtil::null, + LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY, + LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT); + + BOOL isVoiceActive(); + + typedef std::vector > speaker_list_t; + void getSpeakerList(speaker_list_t* speaker_list, BOOL include_text); + LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } + const LLUUID getSessionID(); + + /** + * Removes avaline speaker. + * + * This is a HACK due to server does not send information that Avaline caller ends call. + * It can be removed when server is updated. See EXT-4301 for details + */ + bool removeAvalineSpeaker(const LLUUID& speaker_id) { return removeSpeaker(speaker_id); } + + /** + * Initializes mVoiceModerated depend on LLSpeaker::mModeratorMutedVoice of agent's participant. + * + * Is used only to implement workaround to initialize mVoiceModerated on first join to group chat. See EXT-6937 + */ + void initVoiceModerateMode(); + +protected: + virtual void updateSpeakerList(); + void setSpeakerNotInChannel(LLSpeaker* speackerp); + bool removeSpeaker(const LLUUID& speaker_id); + + typedef std::map > speaker_map_t; + speaker_map_t mSpeakers; + + speaker_list_t mSpeakersSorted; + LLFrameTimer mSpeechTimer; + LLVoiceChannel* mVoiceChannel; + + /** + * time out speakers when they are not part of current session + */ + LLSpeakersDelayActionsStorage* mSpeakerDelayRemover; + + // *TODO: should be moved back into LLIMSpeakerMgr when a way to request the current voice channel + // moderation mode is implemented: See EXT-6937 + bool mVoiceModerated; + + // *TODO: To be removed when a way to request the current voice channel + // moderation mode is implemented: See EXT-6937 + bool mModerateModeHandledFirstTime; +}; + +class LLIMSpeakerMgr : public LLSpeakerMgr +{ + LOG_CLASS(LLIMSpeakerMgr); + +public: + LLIMSpeakerMgr(LLVoiceChannel* channel); + + void updateSpeakers(const LLSD& update); + void setSpeakers(const LLSD& speakers); + + void toggleAllowTextChat(const LLUUID& speaker_id); + + /** + * Mutes/Unmutes avatar for current group voice chat. + * + * It only marks avatar as muted for session and does not use local Agent's Block list. + * It does not mute Agent itself. + * + * @param[in] avatar_id UUID of avatar to be processed + * @param[in] unmute if false - specified avatar will be muted, otherwise - unmuted. + * + * @see moderateVoiceAllParticipants() + */ + void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); + + /** + * Mutes/Unmutes all avatars for current group voice chat. + * + * It only marks avatars as muted for session and does not use local Agent's Block list. + * It calls forceVoiceModeratedMode() in case of session is already in requested state. + * + * @param[in] unmute_everyone if false - avatars will be muted, otherwise - unmuted. + * + * @see moderateVoiceParticipant() + */ + void moderateVoiceAllParticipants(bool unmute_everyone); + + void processSessionUpdate(const LLSD& session_update); + +protected: + virtual void updateSpeakerList(); + + void moderateVoiceSession(const LLUUID& session_id, bool disallow_voice); + + /** + * Process all participants to mute/unmute them according to passed voice session state. + */ + void forceVoiceModeratedMode(bool should_be_muted); + +}; + +class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton +{ + LOG_CLASS(LLActiveSpeakerMgr); + +public: + LLActiveSpeakerMgr(); +protected: + virtual void updateSpeakerList(); +}; + +class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton +{ + LOG_CLASS(LLLocalSpeakerMgr); +public: + LLLocalSpeakerMgr(); + ~LLLocalSpeakerMgr (); +protected: + virtual void updateSpeakerList(); +}; + +#endif // LL_LLSPEAKERS_H + diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 4c72b4801..a39203a8f 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -87,7 +87,7 @@ #include "llstatenums.h" #include "llstatusbar.h" #include "llimview.h" -#include "llfloateractivespeakers.h" +#include "llspeakers.h" #include "lltrans.h" #include "llviewerfoldertype.h" #include "llviewergenericmessage.h" diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 32669f105..ea15621a1 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -40,15 +40,17 @@ // Viewer includes #include "llmutelist.h" // to check for muted avatars #include "llagent.h" +#include "llimpanel.h" // for LLFloaterIMPanel #include "llimview.h" // for LLIMMgr #include "llparcel.h" #include "llviewerparcelmgr.h" //#include "llfirstuse.h" -#include "llvoicechannel.h" // Singu TODO: break out llspeakers.h +#include "llspeakers.h" #include "lltrans.h" #include "llviewercamera.h" #include "llviewernetwork.h" +#include "llvoicechannel.h" #include "llnotificationsutil.h" #include "stringize.h" @@ -3850,9 +3852,12 @@ void LLVivoxVoiceClient::participantUpdatedEvent( // ignore session ID of local chat if (voice_cnl && voice_cnl->getSessionID().notNull()) { - /* Singu TODO: Voice Update: LLIMModel + /* Singu TODO: LLIMModel::getSpeakerManager LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); if (speaker_manager) + */ + if (LLFloaterIMPanel* floaterp = gIMMgr->findFloaterBySession(voice_cnl->getSessionID())) + if (LLSpeakerMgr* speaker_manager = floaterp->getSpeakerManager()) { speaker_manager->update(true); @@ -3863,7 +3868,6 @@ void LLVivoxVoiceClient::participantUpdatedEvent( speaker_manager->initVoiceModerateMode(); } } - */ } } else