/** * @file llspeakingindicatormanager.cpp * @author Mike Antipov * @brief Implementation of SpeackerIndicatorManager class to process registered LLSpeackerIndicator * depend on avatars are in the same voice channel. * * $LicenseInfo:firstyear=2010&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 "llspeakingindicatormanager.h" #include "llvoicechannel.h" #include "llvoiceclient.h" /** * This class intended to control visibility of avatar speaking indicators depend on whether avatars * are in the same voice channel. * * Speaking indicator should be visible for avatars in the same voice channel. See EXT-3976. * * It stores passed instances of LLOutputMonitorCtrl in a multimap by avatar LLUUID. * It observes changing of voice channel and changing of participant list in voice channel. * When voice channel or voice participant list is changed it updates visibility of an appropriate * speaking indicator. * * Several indicators can be registered for the same avatar. */ class SpeakingIndicatorManager : public LLSingleton, LLVoiceClientParticipantObserver { LLSINGLETON(SpeakingIndicatorManager); ~SpeakingIndicatorManager(); LOG_CLASS(SpeakingIndicatorManager); protected: void cleanupSingleton() override; public: /** * Stores passed speaking indicator to control its visibility. * * Registered indicator is set visible if an appropriate avatar is in the same voice channel with Agent. * It ignores instances of Agent's indicator. * * @param speaker_id LLUUID of an avatar whose speaking indicator is registered. * @param speaking_indicator instance of the speaking indicator to be registered. * @param session_id session UUID for which indicator should be shown only. * If this parameter is set registered indicator will be shown only in voice channel * which has the same session id (EXT-5562). */ void registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, const LLUUID& session_id = LLUUID::null); /** * Removes passed speaking indicator from observing. * * @param speaker_id LLUUID of an avatar whose speaking indicator should be unregistered. * @param speaking_indicator instance of the speaking indicator to be unregistered. */ void unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator); /** * Callback of changing voice participant list (from LLVoiceClientParticipantObserver). * * Switches off indicators had been switched on and switches on indicators of current participants list. * There is only a few indicators in lists should be switched off/on. * So, method does not calculate difference between these list it only switches off already * switched on indicators and switches on indicators of voice channel participants */ void onParticipantsChanged() override; private: typedef std::set speaker_ids_t; typedef std::multimap speaking_indicators_mmap_t; typedef speaking_indicators_mmap_t::value_type speaking_indicator_value_t; typedef speaking_indicators_mmap_t::const_iterator indicator_const_iterator; typedef std::pair indicator_range_t; /** * Callback to determine when voice channel is changed. * * It switches all registered speaking indicators off. * To reduce overheads only switched on indicators are processed. */ void sOnCurrentChannelChanged(const LLUUID& session_id); /** * Changes state of indicators specified by LLUUIDs * * @param speakers_uuids - avatars' LLUUIDs whose speaking indicators should be switched * @param switch_on - if TRUE specified indicator will be switched on, off otherwise. */ void switchSpeakerIndicators(const speaker_ids_t& speakers_uuids, BOOL switch_on); /** * Ensures that passed instance of Speaking Indicator does not exist among registered ones. * If yes, it will be removed. */ void ensureInstanceDoesNotExist(LLSpeakingIndicator* const speaking_indicator); /** * Multimap with all registered speaking indicators */ speaking_indicators_mmap_t mSpeakingIndicators; /** * LUUIDs of avatar for which we have speaking indicators switched on. * * Is used to switch off all previously ON indicators when voice participant list is changed. * * @see onChange() */ speaker_ids_t mSwitchedIndicatorsOn; }; ////////////////////////////////////////////////////////////////////////// // PUBLIC SECTION ////////////////////////////////////////////////////////////////////////// void SpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, const LLUUID& session_id) { // do not exclude agent's indicators. They should be processed in the same way as others. See EXT-3889. LL_DEBUGS("SpeakingIndicator") << "Registering indicator: " << speaker_id << "|"<< speaking_indicator << ", session: " << session_id << LL_ENDL; ensureInstanceDoesNotExist(speaking_indicator); speaking_indicator->setTargetSessionID(session_id); speaking_indicator_value_t value_type(speaker_id, speaking_indicator); mSpeakingIndicators.insert(value_type); speaker_ids_t speakers_uuids; BOOL is_in_same_voice = LLVoiceClient::getInstance()->isParticipant(speaker_id); speakers_uuids.insert(speaker_id); switchSpeakerIndicators(speakers_uuids, is_in_same_voice); } void SpeakingIndicatorManager::unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator) { LL_DEBUGS("SpeakingIndicator") << "Unregistering indicator: " << speaker_id << "|"<< speaking_indicator << LL_ENDL; speaking_indicators_mmap_t::iterator it; it = mSpeakingIndicators.find(speaker_id); for (;it != mSpeakingIndicators.end(); ++it) { if (it->second == speaking_indicator) { LL_DEBUGS("SpeakingIndicator") << "Unregistered." << LL_ENDL; mSpeakingIndicators.erase(it); break; } } } ////////////////////////////////////////////////////////////////////////// // PRIVATE SECTION ////////////////////////////////////////////////////////////////////////// SpeakingIndicatorManager::SpeakingIndicatorManager() { LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&SpeakingIndicatorManager::sOnCurrentChannelChanged, this, _1)); LLVoiceClient::addObserver(this); } SpeakingIndicatorManager::~SpeakingIndicatorManager() { } void SpeakingIndicatorManager::cleanupSingleton() { // Don't use LLVoiceClient::getInstance() here without a check, // singleton MAY have already been destroyed. LLVoiceClient::removeObserver(this); } void SpeakingIndicatorManager::sOnCurrentChannelChanged(const LLUUID& /*session_id*/) { switchSpeakerIndicators(mSwitchedIndicatorsOn, FALSE); mSwitchedIndicatorsOn.clear(); } void SpeakingIndicatorManager::onParticipantsChanged() { LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL; speaker_ids_t speakers_uuids; LLVoiceClient::getInstance()->getParticipantList(speakers_uuids); LL_DEBUGS("SpeakingIndicator") << "Switching all OFF, count: " << mSwitchedIndicatorsOn.size() << LL_ENDL; // switch all indicators off switchSpeakerIndicators(mSwitchedIndicatorsOn, FALSE); mSwitchedIndicatorsOn.clear(); LL_DEBUGS("SpeakingIndicator") << "Switching all ON, count: " << speakers_uuids.size() << LL_ENDL; // then switch current voice participants indicators on switchSpeakerIndicators(speakers_uuids, TRUE); } void SpeakingIndicatorManager::switchSpeakerIndicators(const speaker_ids_t& speakers_uuids, BOOL switch_on) { LLVoiceChannel* voice_channel = LLVoiceChannel::getCurrentVoiceChannel(); LLUUID session_id; if (voice_channel) { session_id = voice_channel->getSessionID(); } speaker_ids_t::const_iterator it_uuid = speakers_uuids.begin(); for (; it_uuid != speakers_uuids.end(); ++it_uuid) { LL_DEBUGS("SpeakingIndicator") << "Looking for indicator: " << *it_uuid << LL_ENDL; indicator_range_t it_range = mSpeakingIndicators.equal_range(*it_uuid); indicator_const_iterator it_indicator = it_range.first; bool was_found = false; bool was_switched_on = false; for (; it_indicator != it_range.second; ++it_indicator) { was_found = true; LLSpeakingIndicator* indicator = (*it_indicator).second; was_switched_on = was_switched_on || switch_on; indicator->switchIndicator(switch_on); } if (was_found) { LL_DEBUGS("SpeakingIndicator") << mSpeakingIndicators.count(*it_uuid) << " indicators were found" << LL_ENDL; if (switch_on && !was_switched_on) { LL_DEBUGS("SpeakingIndicator") << "but none of them were switched on" << LL_ENDL; } if (was_switched_on) { // store switched on indicator to be able switch it off mSwitchedIndicatorsOn.insert(*it_uuid); } } } } void SpeakingIndicatorManager::ensureInstanceDoesNotExist(LLSpeakingIndicator* const speaking_indicator) { LL_DEBUGS("SpeakingIndicator") << "Searching for an registered indicator instance: " << speaking_indicator << LL_ENDL; speaking_indicators_mmap_t::iterator it = mSpeakingIndicators.begin(); for (;it != mSpeakingIndicators.end(); ++it) { if (it->second == speaking_indicator) { LL_DEBUGS("SpeakingIndicator") << "Found" << LL_ENDL; break; } } // It is possible with LLOutputMonitorCtrl the same instance of indicator is registered several // times with different UUIDs. This leads to crash after instance is destroyed because the // only one (specified by UUID in unregisterSpeakingIndicator()) is removed from the map. // So, using stored deleted pointer leads to crash. See EXT-4782. if (it != mSpeakingIndicators.end()) { LL_WARNS() << "The same instance of indicator has already been registered, removing it: " << it->first << "|"<< speaking_indicator << LL_ENDL; llassert(it == mSpeakingIndicators.end()); mSpeakingIndicators.erase(it); } } /************************************************************************/ /* LLSpeakingIndicatorManager namespace implementation */ /************************************************************************/ void LLSpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, const LLUUID& session_id) { SpeakingIndicatorManager::instance().registerSpeakingIndicator(speaker_id, speaking_indicator, session_id); } void LLSpeakingIndicatorManager::unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator) { if(SpeakingIndicatorManager::instanceExists()) { SpeakingIndicatorManager::instance().unregisterSpeakingIndicator(speaker_id, speaking_indicator); } } void LLSpeakingIndicatorManager::updateSpeakingIndicators() { if(SpeakingIndicatorManager::instanceExists()) { SpeakingIndicatorManager::instance().onParticipantsChanged(); } } // EOF