diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index a4fc095727d0158641b346f67ce97651a40c9a7d..c29a3a0035a3dbded4adbab335eb3bcb82389578 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10070,6 +10070,18 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
+    <key>SpeakerParticipantRemoveDelay</key>
+    <map>
+      <key>Comment</key>
+      <string>Timeout to remove participants who is not in channel before removed from list of active speakers (text/voice chat)</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>F32</string>
+      <key>Value</key>
+      <real>10.0</real>
+    </map>
+
     <key>UseStartScreen</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llpanelimcontrolpanel.cpp b/indra/newview/llpanelimcontrolpanel.cpp
index b1cdb4d81f648f3ef9fe81d57c19e60ad9191dbe..86bdee7c7d58dab77e65819ce66a25336eb1b480 100644
--- a/indra/newview/llpanelimcontrolpanel.cpp
+++ b/indra/newview/llpanelimcontrolpanel.cpp
@@ -264,9 +264,6 @@ LLPanelGroupControlPanel::~LLPanelGroupControlPanel()
 // virtual
 void LLPanelGroupControlPanel::draw()
 {
-	//Remove event does not raised until speakerp->mActivityTimer.hasExpired() is false, see LLSpeakerManager::update()
-	//so we need update it to raise needed event
-	mSpeakerManager->update(true);
 	// Need to resort the participant list if it's in sort by recent speaker order.
 	if (mParticipantList)
 		mParticipantList->updateRecentSpeakersOrder();
diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp
index 0dd9203c6d9992774c82dde2190034f36197d846..9608cd1263766a72ae959744ab76aad4d6c395b7 100644
--- a/indra/newview/llspeakers.cpp
+++ b/indra/newview/llspeakers.cpp
@@ -44,7 +44,6 @@
 #include "llvoavatar.h"
 #include "llworld.h"
 
-const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers
 const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f);
 const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f);
 
@@ -73,8 +72,6 @@ LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerTy
 	}
 
 	gVoiceClient->setUserVolume(id, LLMuteList::getInstance()->getSavedResidentVolume(id));
-
-	mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT);
 }
 
 
@@ -164,6 +161,89 @@ bool LLSortRecentSpeakers::operator()(const LLPointer<LLSpeaker> lhs, const LLPo
 	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;
+}
+
+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())
+	{
+		delete it_speaker->second;
+		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);
+	}
+
+	// do not return true to avoid deleting of an timer twice:
+	// in LLSpeakersDelayActionsStorage::unsetActionTimer() & LLEventTimer::updateClass()
+	return false;
+}
+
 
 //
 // LLSpeakerMgr
@@ -172,10 +252,14 @@ bool LLSortRecentSpeakers::operator()(const LLPointer<LLSpeaker> lhs, const LLPo
 LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : 
 	mVoiceChannel(channelp)
 {
+	static LLUICachedControl<F32> remove_delay ("SpeakerParticipantRemoveDelay", 10.0);
+
+	mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLSpeakerMgr::removeSpeaker, this, _1), remove_delay);
 }
 
 LLSpeakerMgr::~LLSpeakerMgr()
 {
+	delete mSpeakerDelayRemover;
 }
 
 LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type)
@@ -198,7 +282,6 @@ LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::strin
 		{
 			// keep highest priority status (lowest value) instead of overriding current value
 			speakerp->mStatus = llmin(speakerp->mStatus, status);
-			speakerp->mActivityTimer.resetWithExpiry(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
@@ -210,6 +293,8 @@ LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::strin
 		}
 	}
 
+	mSpeakerDelayRemover->unsetActionTimer(speakerp->mID);
+
 	return speakerp;
 }
 
@@ -314,7 +399,7 @@ void LLSpeakerMgr::update(BOOL resort_ok)
 	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 != mSpeakersSorted.end(); ++sorted_speaker_it)
 	{
 		LLPointer<LLSpeaker> speakerp = *sorted_speaker_it;
 		
@@ -327,19 +412,6 @@ void LLSpeakerMgr::update(BOOL resort_ok)
 
 		// 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;
-		}
 	}
 }
 
@@ -363,6 +435,35 @@ void LLSpeakerMgr::updateSpeakerList()
 	}
 }
 
+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;
+		}
+	}
+
+	fireEvent(new LLSpeakerListChangeEvent(this, speaker_id), "remove");
+
+	update(TRUE);
+
+	return false;
+}
+
 LLPointer<LLSpeaker> LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id)
 {
 	//In some conditions map causes crash if it is empty(Windows only), adding check (EK)
@@ -511,9 +612,7 @@ void LLIMSpeakerMgr::updateSpeakers(const LLSD& update)
 			{
 				if (agent_data["transition"].asString() == "LEAVE" && speakerp.notNull())
 				{
-					speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL;
-					speakerp->mDotColor = INACTIVE_COLOR;
-					speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT);
+					setSpeakerNotInChannel(speakerp);
 				}
 				else if (agent_data["transition"].asString() == "ENTER")
 				{
@@ -563,9 +662,7 @@ void LLIMSpeakerMgr::updateSpeakers(const LLSD& update)
 			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.resetWithExpiry(SPEAKER_TIMEOUT);
+				setSpeakerNotInChannel(speakerp);
 			}
 			else if ( agent_transition == "ENTER")
 			{
@@ -734,12 +831,13 @@ void LLActiveSpeakerMgr::updateSpeakerList()
 	mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel();
 
 	// always populate from active voice channel
-	if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel)
+	if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) //MA: seems this is always false
 	{
 		fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear");
 		mSpeakers.clear();
 		mSpeakersSorted.clear();
 		mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel();
+		mSpeakerDelayRemover->removeAllTimers();
 	}
 	LLSpeakerMgr::updateSpeakerList();
 
@@ -800,9 +898,7 @@ void LLLocalSpeakerMgr::updateSpeakerList()
 			LLVOAvatar* avatarp = (LLVOAvatar*)gObjectList.findObject(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.resetWithExpiry(SPEAKER_TIMEOUT);
+				setSpeakerNotInChannel(speakerp);
 			}
 		}
 	}
diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h
index da8dfdf548e639dbaf949c8065bc7ca4fe568cd5..63237204c8620e2b1e40d2ef04864471a2fe1db0 100644
--- a/indra/newview/llspeakers.h
+++ b/indra/newview/llspeakers.h
@@ -73,7 +73,6 @@ class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LL
 	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?
 	BOOL			mHasLeftCurrentCall;	// has this speaker left the current voice call?
 	LLColor4		mDotColor;
@@ -120,6 +119,92 @@ class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent
 	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<bool(const LLUUID&)>	action_callback_t;
+	typedef std::map<LLUUID, LLSpeakerActionTimer*> 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();
+
+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 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.
+	 * It always returns false to not use LLEventTimer::updateClass functionality of timer deleting.
+	 *
+	 * @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
 {
 public:
@@ -144,6 +229,8 @@ class LLSpeakerMgr : public LLOldEvents::LLObservable
 
 protected:
 	virtual void updateSpeakerList();
+	void setSpeakerNotInChannel(LLSpeaker* speackerp);
+	bool removeSpeaker(const LLUUID& speaker_id);
 
 	typedef std::map<LLUUID, LLPointer<LLSpeaker> > speaker_map_t;
 	speaker_map_t		mSpeakers;
@@ -151,6 +238,11 @@ class LLSpeakerMgr : public LLOldEvents::LLObservable
 	speaker_list_t		mSpeakersSorted;
 	LLFrameTimer		mSpeechTimer;
 	LLVoiceChannel*		mVoiceChannel;
+
+	/**
+	 * time out speakers when they are not part of current session
+	 */
+	LLSpeakersDelayActionsStorage* mSpeakerDelayRemover;
 };
 
 class LLIMSpeakerMgr : public LLSpeakerMgr