diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index dd5774929e73f079e1535e45f1a0d96e5270fcc0..cb9177587fd9836e43a56f821a9830628b60fa74 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10606,6 +10606,17 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
+    <key>VoiceEffectExpiryWarningTime</key>
+    <map>
+      <key>Comment</key>
+      <string>How much notice to give of voice effect subscriptions expiry, in seconds.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>F32</string>
+      <key>Value</key>
+      <real>259200.0</real>
+    </map>
     <key>AutoDisengageMic</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llfloaternotificationsconsole.cpp b/indra/newview/llfloaternotificationsconsole.cpp
index b744bff084012e3d3ab0420010b201b0ca591135..105d7f92015abd2a18c53f02c08df1468e1e8ecc 100644
--- a/indra/newview/llfloaternotificationsconsole.cpp
+++ b/indra/newview/llfloaternotificationsconsole.cpp
@@ -184,7 +184,7 @@ BOOL LLFloaterNotificationConsole::postBuild()
 	addChannel("Ignore");
 	addChannel("Visible", true);
 	// all the ones below attach to the Visible channel
-	addChannel("History");
+	addChannel("Persistent");
 	addChannel("Alerts");
 	addChannel("AlertModal");
 	addChannel("Group Notifications");
diff --git a/indra/newview/llfloatervoiceeffect.cpp b/indra/newview/llfloatervoiceeffect.cpp
index e30a50fab7b3fd3c7b13f5fd285d41d5c04428a2..f31ab969853d9d1932ab33edebfe3ed167fb3baa 100644
--- a/indra/newview/llfloatervoiceeffect.cpp
+++ b/indra/newview/llfloatervoiceeffect.cpp
@@ -230,9 +230,9 @@ void LLFloaterVoiceEffect::updateControls()
 }
 
 // virtual
-void LLFloaterVoiceEffect::onVoiceEffectChanged(bool new_effects)
+void LLFloaterVoiceEffect::onVoiceEffectChanged(bool effect_list_updated)
 {
-	if (new_effects)
+	if (effect_list_updated)
 	{
 		refreshEffectList();
 	}
diff --git a/indra/newview/llfloatervoiceeffect.h b/indra/newview/llfloatervoiceeffect.h
index ed38cd6925f7ba300494712a1b534151f7e1b8c8..46b241bd172752a8e2f1c3adeb98c6422f56af77 100644
--- a/indra/newview/llfloatervoiceeffect.h
+++ b/indra/newview/llfloatervoiceeffect.h
@@ -57,7 +57,7 @@ private:
 	void updateControls();
 
 	/// Called by voice effect provider when voice effect list is changed.
-	virtual void onVoiceEffectChanged(bool new_effects);
+	virtual void onVoiceEffectChanged(bool effect_list_updated);
 
 	void onClickRecord();
 	void onClickPlay();
diff --git a/indra/newview/llpanelvoiceeffect.cpp b/indra/newview/llpanelvoiceeffect.cpp
index 140feb7a54b2750542946cad73755b4da86e4f77..4f73f38edcd2886f52d33a13d4317f0291415655 100644
--- a/indra/newview/llpanelvoiceeffect.cpp
+++ b/indra/newview/llpanelvoiceeffect.cpp
@@ -110,7 +110,7 @@ void LLPanelVoiceEffect::onCommitVoiceEffect()
 }
 
 // virtual
-void LLPanelVoiceEffect::onVoiceEffectChanged(bool new_effects)
+void LLPanelVoiceEffect::onVoiceEffectChanged(bool effect_list_updated)
 {
 	update();
 }
diff --git a/indra/newview/llpanelvoiceeffect.h b/indra/newview/llpanelvoiceeffect.h
index 008123a4e45cdcc35e08a5c37bc5ad4610f36749..bd7bdd04f21d2fdf05201bbf789002e34af6c119 100644
--- a/indra/newview/llpanelvoiceeffect.h
+++ b/indra/newview/llpanelvoiceeffect.h
@@ -56,7 +56,7 @@ private:
 	void update();
 
 	/// Called by voice effect provider when voice effect list is changed.
-	virtual void onVoiceEffectChanged(bool new_effects);
+	virtual void onVoiceEffectChanged(bool effect_list_updated);
 
 	// Fixed entries in the voice effect list
 	typedef enum e_voice_effect_combo_items
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index 7f8f979da01410d987bc4de5e54a7eda9d098aef..744ec8402323238501b2dcf7f458f2d55263a86a 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -266,7 +266,7 @@ class LLVoiceEffectObserver
 {
 public:
 	virtual ~LLVoiceEffectObserver() { }
-	virtual void onVoiceEffectChanged(bool new_effects) = 0;
+	virtual void onVoiceEffectChanged(bool effect_list_updated) = 0;
 };
 
 typedef std::multimap<const std::string, const LLUUID, LLDictionaryLess> voice_effect_list_t;
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index 304321b3615eff21b460cfb9bdde0aca6011af92..d786b4d928c682453fcb9bf3e4b000c236c185bd 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -60,6 +60,7 @@
 #include "llviewerparcelmgr.h"
 //#include "llfirstuse.h"
 #include "llspeakers.h"
+#include "lltrans.h"
 #include "llviewerwindow.h"
 #include "llviewercamera.h"
 
@@ -96,6 +97,9 @@ const int MAX_LOGIN_RETRIES = 12;
 // blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability.
 const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50;
 
+// How often to check for expired voice fonts
+const F32 VOICE_FONT_EXPIRY_INTERVAL = 1.f;
+
 // Maximum length of capture buffer recordings
 const F32 CAPTURE_BUFFER_MAX_TIME = 15.f;
 
@@ -781,7 +785,7 @@ void LLVivoxVoiceClient::stateMachine()
 			closeSocket();
 			deleteAllSessions();
 			deleteAllBuddies();
-			deleteVoiceFonts();
+			deleteAllVoiceFonts();
 			deleteVoiceFontTemplates();
 
 			mConnectorHandle.clear();
@@ -1372,6 +1376,10 @@ void LLVivoxVoiceClient::stateMachine()
 			
 		//MARK: stateVoiceFontsReceived
 		case stateVoiceFontsReceived:	// Voice font list received
+			// Set up the timer to check for expiring voice fonts
+			mVoiceFontExpiryTimer.start();
+			mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+
 #if USE_SESSION_GROUPS			
 			// create the main session group
 			setState(stateCreatingSessionGroup);
@@ -1600,6 +1608,13 @@ void LLVivoxVoiceClient::stateMachine()
 					enforceTether();
 				}
 				
+				// Do notifications for expiring Voice Fonts.
+				if (mVoiceFontExpiryTimer.hasExpired())
+				{
+					expireVoiceFonts();
+					mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+				}
+
 				// Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often
 				// -- the user can only click so fast) or every 10hz, whichever is sooner.
 				// Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged.
@@ -1669,7 +1684,7 @@ void LLVivoxVoiceClient::stateMachine()
 			mAccountHandle.clear();
 			deleteAllSessions();
 			deleteAllBuddies();
-			deleteVoiceFonts();
+			deleteAllVoiceFonts();
 			deleteVoiceFontTemplates();
 
 			if(mVoiceEnabled && !mRelogRequested)
@@ -6461,7 +6476,6 @@ LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id)
 
 	voiceFontEntry *font = iter->second;
 	sd["name"] = font->mName;
-	sd["expired"] = font->mHasExpired;
 	sd["expiry_date"] = font->mExpirationDate;
 	sd["is_new"] = font->mIsNew;
 
@@ -6471,7 +6485,6 @@ LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id)
 LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) :
 	mID(id),
 	mFontIndex(0),
-	mHasExpired(false),
 	mFontType(VOICE_FONT_TYPE_NONE),
 	mFontStatus(VOICE_FONT_STATUS_NONE),
 	mIsNew(false)
@@ -6487,7 +6500,7 @@ void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists)
 	if (clear_lists)
 	{
 		mVoiceFontsReceived = false;
-		deleteVoiceFonts();
+		deleteAllVoiceFonts();
 		deleteVoiceFontTemplates();
 	}
 
@@ -6533,12 +6546,23 @@ void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
 	voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap;
 	voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList;
 
-	// Check whether we've seen this font before and create an entry for it if not.
+	// Check whether we've seen this font before.
 	voice_font_map_t::iterator iter = font_map.find(font_id);
 	bool new_font = (iter == font_map.end());
+
+	// If it is a new (unexpired) font create a new entry, otherwise update the existing one.
 	if (new_font)
 	{
-		font = new voiceFontEntry(font_id);
+		if (!has_expired)
+		{
+			font = new voiceFontEntry(font_id);
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id
+			<< " (" << font_index << ") : " << name << " has expired." << LL_ENDL;
+		}
+
 	}
 	else
 	{
@@ -6547,15 +6571,47 @@ void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
 
 	if (font)
 	{
+		// Remove fonts that have expired since we last saw them.
+		if (has_expired)
+		{
+			LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id
+			<< " (" << font_index << ") : " << name << " has expired, removing."
+			<< LL_ENDL;
+
+			deleteVoiceFont(font_id);
+			return;
+		}
+
 		font->mFontIndex = font_index;
 		// Use the description for the human readable name if available, as the
 		// "name" may be a UUID.
 		font->mName = description.empty() ? name : description;
 		font->mExpirationDate = expiration_date;
-		font->mHasExpired = has_expired;
 		font->mFontType = font_type;
 		font->mFontStatus = font_status;
 
+		F64 expiry_time = 0.f;
+
+		// Set the expiry timer to trigger a notification when the voice font can no longer be used.
+		font->mExpiryTimer.start();
+		expiry_time = expiration_date.secondsSinceEpoch() - LLTimer::getTotalSeconds();
+		font->mExpiryTimer.setTimerExpirySec(expiry_time);
+
+		// Set the warning timer to some interval before actual expiry.
+		F64 warning_time = (F64)gSavedSettings.getF32("VoiceEffectExpiryWarningTime");
+		if (warning_time > 0.f)
+		{
+			font->mExpiryWarningTimer.start();
+			expiry_time = (expiration_date.secondsSinceEpoch() - warning_time)
+							- LLTimer::getTotalSeconds();
+			font->mExpiryWarningTimer.setTimerExpirySec(expiry_time);
+		}
+		else
+		{
+			// Disable the warning timer.
+			font->mExpiryWarningTimer.stop();
+		}
+
 		 // Only flag it as a new font if we have already seen the font list.
 		if (!template_font && mVoiceFontsReceived && new_font)
 		{
@@ -6563,9 +6619,16 @@ void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
 			mVoiceFontsNew = true;
 		}
 
+		if (new_font)
+		{
+			font_map.insert(voice_font_map_t::value_type(font->mID, font));
+			font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID));
+		}
+
+		// Debugging stuff
+
 		LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id
-			<< " (" << font_index << ") : " << name << (has_expired?" (Expired)":"")
-			<< LL_ENDL;
+		<< " (" << font_index << ") : " << name << LL_ENDL;
 
 		if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN)
 		{
@@ -6575,16 +6638,108 @@ void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
 		{
 			LL_DEBUGS("Voice") << "Unknown voice font status: " << font_status << LL_ENDL;
 		}
+	}
+}
 
-		if (new_font)
+void LLVivoxVoiceClient::expireVoiceFonts()
+{
+	// *TODO: If we are selling voice fonts in packs, there are probably
+	// going to be a number of fonts with the same expiration time, so would
+	// be more efficient to just keep a list of expiration times rather
+	// than checking each font individually.
+
+	bool have_expired = false;
+	bool will_expire = false;
+	bool expired_in_use = false;
+
+	LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault();
+
+	voice_font_map_t::iterator iter;
+	for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+	{
+		voiceFontEntry* voice_font = iter->second;
+		LLTimer& expiry_timer  = voice_font->mExpiryTimer;
+		LLTimer& warning_timer = voice_font->mExpiryWarningTimer;
+
+		// Check for expired voice fonts
+		if (expiry_timer.getStarted() && expiry_timer.hasExpired())
 		{
-			font_map.insert(voice_font_map_t::value_type(font->mID, font));
-			font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID));
+			// Check whether it is the active voice font
+			if (voice_font->mID == current_effect)
+			{
+				// Reset to no voice effect.
+				setVoiceEffect(LLUUID::null);
+				expired_in_use = true;
+			}
+			deleteVoiceFont(voice_font->mID);
+			have_expired = true;
+		}
+
+		// Check for voice fonts that will expire in less that the warning time
+		if (warning_timer.getStarted() && warning_timer.hasExpired())
+		{
+			will_expire = true;
+			warning_timer.stop();
+		}
+	}
+
+	LLSD args;
+	args["URL"] = LLTrans::getString("voice_morphing_url");
+
+	// Give a notification if any voice fonts have expired.
+	if (have_expired)
+	{
+		if (expired_in_use)
+		{
+			LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args);
+		}
+		else
+		{
+			LLNotificationsUtil::add("VoiceEffectsExpired", args);
+		}
+
+		// Refresh voice font lists in the UI.
+		notifyVoiceFontObservers(true);
+	}
+
+	// Give a warning notification if any voice fonts are due to expire.
+	if (will_expire)
+	{
+		S32 seconds = gSavedSettings.getF32("VoiceEffectExpiryWarningTime");
+		args["INTERVAL"] = llformat("%d", (seconds / SEC_PER_DAY));
+
+		LLNotificationsUtil::add("VoiceEffectsWillExpire", args);
+	}
+}
+
+void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id)
+{
+	// Remove the entry from the voice font list.
+	voice_effect_list_t::iterator list_iter = mVoiceFontList.begin();
+	while (list_iter != mVoiceFontList.end())
+	{
+		if (list_iter->second == id)
+		{
+			mVoiceFontList.erase(list_iter++);
+		}
+		else
+		{
+			++list_iter;
 		}
 	}
+
+	// Find the entry in the voice font map and erase its data.
+	voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id);
+	if (map_iter != mVoiceFontMap.end())
+	{
+		delete map_iter->second;
+	}
+
+	// Remove the entry from the voice font map.
+	mVoiceFontMap.erase(map_iter);
 }
 
-void LLVivoxVoiceClient::deleteVoiceFonts()
+void LLVivoxVoiceClient::deleteAllVoiceFonts()
 {
 	mVoiceFontList.clear();
 
@@ -6723,14 +6878,14 @@ void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer)
 	mVoiceFontObservers.erase(observer);
 }
 
-void LLVivoxVoiceClient::notifyVoiceFontObservers(bool new_fonts)
+void LLVivoxVoiceClient::notifyVoiceFontObservers(bool lists_changed)
 {
 	for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin();
 		 it != mVoiceFontObservers.end();
 		 )
 	{
 		LLVoiceEffectObserver* observer = *it;
-		observer->onVoiceEffectChanged(new_fonts);
+		observer->onVoiceEffectChanged(lists_changed);
 		// In case onVoiceEffectChanged() deleted an entry.
 		it = mVoiceFontObservers.upper_bound(observer);
 	}
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
index 130fdac1463e691e49ee664a03d18706742981f2..d71fe132c588e99d1e1b039b75b809da77841364 100644
--- a/indra/newview/llvoicevivox.h
+++ b/indra/newview/llvoicevivox.h
@@ -857,7 +857,9 @@ private:
 
 	// Voice Fonts
 
-	void deleteVoiceFonts();
+	void expireVoiceFonts();
+	void deleteVoiceFont(const LLUUID& id);
+	void deleteAllVoiceFonts();
 	void deleteVoiceFontTemplates();
 
 	S32 getVoiceFontIndex(const LLUUID& id) const;
@@ -867,7 +869,7 @@ private:
 	void accountGetTemplateFontsSendMessage();
 	void sessionSetVoiceFontSendMessage(sessionState *session);
 
-	void notifyVoiceFontObservers(bool new_fonts = false);
+	void notifyVoiceFontObservers(bool lists_changed = false);
 
 	typedef enum e_voice_font_type
 	{
@@ -894,10 +896,12 @@ private:
 		S32			mFontIndex;
 		std::string mName;
 		LLDate		mExpirationDate;
-		bool		mHasExpired;
 		S32			mFontType;
 		S32			mFontStatus;
 		bool		mIsNew;
+
+		LLTimer		mExpiryTimer;
+		LLTimer		mExpiryWarningTimer;
 	};
 
 	bool mVoiceFontsReceived;
@@ -912,6 +916,9 @@ private:
 	typedef std::set<LLVoiceEffectObserver*> voice_font_observer_set_t;
 	voice_font_observer_set_t mVoiceFontObservers;
 
+	LLTimer	mVoiceFontExpiryTimer;
+
+
 	// Audio capture buffer
 
 	void captureBufferRecordStartSendMessage();
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 999f804e7191c0d4d8308411eb4a12b1288265d4..6b5659f6c0fdfb35502643c766cd70773216e004 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -5956,6 +5956,39 @@ We are creating a voice channel for you. This may take up to one minute.
     <unique/>
   </notification>
 
+  <notification
+   icon="notify.tga"
+   name="VoiceEffectsExpired"
+   sound="UISndAlert"
+   persist="true"
+   type="notify">
+Subscribed Voice Effects have expired.
+[[URL] Renew your subscription] to reactivate them.
+    <unique/>
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="VoiceEffectsExpiredInUse"
+   sound="UISndAlert"
+   persist="true"
+   type="notify">
+The active Voice Effect has expired, your normal voice settings have been applied.
+[[URL] Renew your subscription] to reactivate it.
+    <unique/>
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="VoiceEffectsWillExpire"
+   sound="UISndAlert"
+   persist="true"
+   type="notify">
+Voice Effects will expire in less than [INTERVAL] days.
+[[URL] Renew your subscription] or they will be removed.
+    <unique/>
+  </notification>
+
   <notification
    icon="notifytip.tga"
    name="Cannot enter parcel: not a group member"