Bug 825674 - Installed apps do not get affected by the hardware volume control, r=sicking
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 09 Jan 2013 08:18:16 +0100
changeset 128072 b3110cc625529cda6db0df508545926929674ca9
parent 128071 f77ccdb45a90188f922d48339b097a807fbd9998
child 128073 4961e3a15ed34aa8b9e76177725bc8c3f2d19c07
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs825674
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 825674 - Installed apps do not get affected by the hardware volume control, r=sicking
b2g/chrome/content/shell.js
dom/audiochannel/AudioChannelAgent.cpp
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/AudioChannelServiceChild.cpp
dom/audiochannel/AudioChannelServiceChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -969,25 +969,16 @@ window.addEventListener('ContentStart', 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
       type: 'audio-channel-changed',
       channel: aData
     });
 }, "audio-channel-changed", false);
 })();
 
-(function audioChannelChangedTracker() {
-  Services.obs.addObserver(function(aSubject, aTopic, aData) {
-    shell.sendChromeEvent({
-      type: 'audio-channel-changed',
-      channel: aData
-    });
-}, "audio-channel-changed", false);
-})();
-
 (function recordingStatusTracker() {
   let gRecordingActiveCount = 0;
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     let oldCount = gRecordingActiveCount;
     if (aData == "starting") {
       gRecordingActiveCount += 1;
     } else if (aData == "shutdown") {
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -67,17 +67,17 @@ NS_IMETHODIMP AudioChannelAgent::StartPl
   AudioChannelService *service = AudioChannelService::GetAudioChannelService();
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
       service == nullptr) {
     return NS_ERROR_FAILURE;
   }
 
   service->RegisterAudioChannelAgent(this,
     static_cast<AudioChannelType>(mAudioChannelType));
-  *_retval = !service->GetMuted(static_cast<AudioChannelType>(mAudioChannelType), !mVisible);
+  *_retval = !service->GetMuted(this, !mVisible);
   mIsRegToService = true;
   return NS_OK;
 }
 
 /* void stopPlaying (); */
 NS_IMETHODIMP AudioChannelAgent::StopPlaying(void)
 {
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
@@ -94,23 +94,21 @@ NS_IMETHODIMP AudioChannelAgent::StopPla
 /* void setVisibilityState (in boolean visible); */
 NS_IMETHODIMP AudioChannelAgent::SetVisibilityState(bool visible)
 {
   bool oldVisibility = mVisible;
 
   mVisible = visible;
   if (mIsRegToService && oldVisibility != mVisible && mCallback != nullptr) {
     AudioChannelService *service = AudioChannelService::GetAudioChannelService();
-    mCallback->CanPlayChanged(!service->GetMuted(static_cast<AudioChannelType>(mAudioChannelType),
-       !mVisible));
+    mCallback->CanPlayChanged(!service->GetMuted(this, !mVisible));
   }
   return NS_OK;
 }
 
 void AudioChannelAgent::NotifyAudioChannelStateChanged()
 {
   if (mCallback != nullptr) {
     AudioChannelService *service = AudioChannelService::GetAudioChannelService();
-    mCallback->CanPlayChanged(!service->GetMuted(static_cast<AudioChannelType>(mAudioChannelType),
-      !mVisible));
+    mCallback->CanPlayChanged(!service->GetMuted(this, !mVisible));
   }
 }
 
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -61,17 +61,17 @@ AudioChannelService::Shutdown()
   if (gAudioChannelService) {
     gAudioChannelService = nullptr;
   }
 }
 
 NS_IMPL_ISUPPORTS0(AudioChannelService)
 
 AudioChannelService::AudioChannelService()
-: mCurrentHigherChannel(AUDIO_CHANNEL_NORMAL)
+: mCurrentHigherChannel(AUDIO_CHANNEL_LAST)
 {
   // Creation of the hash table.
   mAgents.Init();
 
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->AddObserver(this, "ipc:content-shutdown", false);
@@ -82,181 +82,210 @@ AudioChannelService::AudioChannelService
 AudioChannelService::~AudioChannelService()
 {
 }
 
 void
 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                                AudioChannelType aType)
 {
-  mAgents.Put(aAgent, aType);
+  AudioChannelAgentData data = { aType,
+                                 true /* mElementHidden */,
+                                 true /* mMuted */ };
+  mAgents.Put(aAgent, data);
   RegisterType(aType, CONTENT_PARENT_UNKNOWN_CHILD_ID);
 }
 
 void
 AudioChannelService::RegisterType(AudioChannelType aType, uint64_t aChildID)
 {
-  mChannelCounters[aType].AppendElement(aChildID);
+  AudioChannelInternalType type = GetInternalType(aType, true);
+  mChannelCounters[type].AppendElement(aChildID);
 
   // In order to avoid race conditions, it's safer to notify any existing
   // agent any time a new one is registered.
-  Notify();
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    SendAudioChannelChangedNotification();
+    Notify();
+  }
 }
 
 void
 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
-  AudioChannelType type;
-  if (!mAgents.Get(aAgent, &type)) {
+  AudioChannelAgentData data;
+  if (!mAgents.Get(aAgent, &data)) {
     return;
   }
 
   mAgents.Remove(aAgent);
-  UnregisterType(type, CONTENT_PARENT_UNKNOWN_CHILD_ID);
+  UnregisterType(data.mType, data.mElementHidden, CONTENT_PARENT_UNKNOWN_CHILD_ID);
 }
 
 void
-AudioChannelService::UnregisterType(AudioChannelType aType, uint64_t aChildID)
+AudioChannelService::UnregisterType(AudioChannelType aType,
+                                    bool aElementHidden,
+                                    uint64_t aChildID)
 {
   // The array may contain multiple occurrence of this appId but
   // this should remove only the first one.
-  mChannelCounters[aType].RemoveElement(aChildID);
-
-  bool isNoChannelUsed = true;
-  for (int32_t type = AUDIO_CHANNEL_NORMAL;
-         type <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
-         ++type) {
-    if (!mChannelCounters[type].IsEmpty()) {
-      isNoChannelUsed = false;
-      break;
-    }
-  }
-
-  if (isNoChannelUsed) {
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    obs->NotifyObservers(nullptr, "audio-channel-changed", NS_LITERAL_STRING("default").get());
-    mCurrentHigherChannel = AUDIO_CHANNEL_NORMAL;
-    return;
-  }
+  AudioChannelInternalType type = GetInternalType(aType, aElementHidden);
+  mChannelCounters[type].RemoveElement(aChildID);
 
   // In order to avoid race conditions, it's safer to notify any existing
   // agent any time a new one is registered.
-  Notify();
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    SendAudioChannelChangedNotification();
+    Notify();
+  }
 }
 
 bool
-AudioChannelService::GetMuted(AudioChannelType aType, bool aElementHidden)
+AudioChannelService::GetMuted(AudioChannelAgent* aAgent, bool aElementHidden)
 {
-  // We are not visible, maybe we have to mute:
-  if (aElementHidden) {
-    switch (aType) {
-      case AUDIO_CHANNEL_NORMAL:
-        return true;
+  AudioChannelAgentData data;
+  if (!mAgents.Get(aAgent, &data)) {
+    return true;
+  }
+
+  bool muted = GetMutedInternal(data.mType, CONTENT_PARENT_UNKNOWN_CHILD_ID,
+                                aElementHidden, data.mElementHidden);
 
-      case AUDIO_CHANNEL_CONTENT:
-      {
-        // If we have more than 1 using the content channel,
-        // this must be muted.
-        uint32_t childId = CONTENT_PARENT_UNKNOWN_CHILD_ID;
-        bool empty = true;
-        for (uint32_t i = 0;
-             i < mChannelCounters[AUDIO_CHANNEL_CONTENT].Length();
-             ++i) {
-          if (empty) {
-            childId = mChannelCounters[AUDIO_CHANNEL_CONTENT][i];
-            empty = false;
-          }
-          else if (childId != mChannelCounters[AUDIO_CHANNEL_CONTENT][i])
-            return true;
-        }
-        break;
-      }
+  // Update visibility.
+  if (data.mElementHidden != aElementHidden || data.mMuted != muted) {
+    data.mElementHidden = aElementHidden;
+    data.mMuted = muted;
+    mAgents.Put(aAgent, data);
+  }
+
+  SendAudioChannelChangedNotification();
+  return muted;
+}
 
-      case AUDIO_CHANNEL_NOTIFICATION:
-      case AUDIO_CHANNEL_ALARM:
-      case AUDIO_CHANNEL_TELEPHONY:
-      case AUDIO_CHANNEL_RINGER:
-      case AUDIO_CHANNEL_PUBLICNOTIFICATION:
-        // Nothing to do
-        break;
+bool
+AudioChannelService::GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
+                                      bool aElementHidden, bool aElementWasHidden)
+{
+  // Calculating the new and old type and update the hashtable if needed.
+  AudioChannelInternalType newType = GetInternalType(aType, aElementHidden);
+  AudioChannelInternalType oldType = GetInternalType(aType, aElementWasHidden);
 
-      case AUDIO_CHANNEL_LAST:
-        MOZ_NOT_REACHED();
-        return false;
-    }
+  if (newType != oldType) {
+    mChannelCounters[newType].AppendElement(aChildID);
+    mChannelCounters[oldType].RemoveElement(aChildID);
   }
 
   bool muted = false;
 
-  // Priorities:
-  switch (aType) {
-    case AUDIO_CHANNEL_NORMAL:
-    case AUDIO_CHANNEL_CONTENT:
-      muted = !mChannelCounters[AUDIO_CHANNEL_NOTIFICATION].IsEmpty() ||
-              !mChannelCounters[AUDIO_CHANNEL_ALARM].IsEmpty() ||
-              !mChannelCounters[AUDIO_CHANNEL_TELEPHONY].IsEmpty() ||
-              !mChannelCounters[AUDIO_CHANNEL_RINGER].IsEmpty() ||
-              !mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION].IsEmpty();
-      break;
-
-    case AUDIO_CHANNEL_NOTIFICATION:
-    case AUDIO_CHANNEL_ALARM:
-    case AUDIO_CHANNEL_TELEPHONY:
-    case AUDIO_CHANNEL_RINGER:
-      muted = ChannelsActiveWithHigherPriorityThan(aType);
-      break;
-
-    case AUDIO_CHANNEL_PUBLICNOTIFICATION:
-      break;
-
-    case AUDIO_CHANNEL_LAST:
-      MOZ_NOT_REACHED();
-      return false;
+  // We are not visible, maybe we have to mute.
+  if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
+      (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
+       (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
+        HasMoreThanOneContentChannelHidden()))) {
+    muted = true;
   }
 
-  // Notification if needed.
   if (!muted) {
-
-    // Calculating the most important unmuted channel:
-    AudioChannelType higher = AUDIO_CHANNEL_NORMAL;
-    for (int32_t type = AUDIO_CHANNEL_NORMAL;
-         type <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
-         ++type) {
-      if (!mChannelCounters[type].IsEmpty()) {
-        higher = (AudioChannelType)type;
-      }
-    }
-
-    if (higher != mCurrentHigherChannel) {
-      mCurrentHigherChannel = higher;
-
-      nsString channelName;
-      channelName.AssignASCII(ChannelName(mCurrentHigherChannel));
-
-      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-      obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
-    }
+    MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
+    muted = ChannelsActiveWithHigherPriorityThan(newType);
   }
 
   return muted;
 }
 
 bool
 AudioChannelService::ContentChannelIsActive()
 {
-  return mChannelCounters[AUDIO_CHANNEL_CONTENT].Length() > 0;
+  return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
+         !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty();
+}
+
+bool
+AudioChannelService::HasMoreThanOneContentChannelHidden()
+{
+  uint32_t childId = CONTENT_PARENT_UNKNOWN_CHILD_ID;
+  bool empty = true;
+  for (uint32_t i = 0;
+       i < mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Length();
+       ++i) {
+    if (empty) {
+      childId = mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN][i];
+      empty = false;
+    } else if (childId != mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN][i]) {
+      return true;
+    }
+  }
+
+  return false;
 }
 
-static PLDHashOperator
-NotifyEnumerator(AudioChannelAgent* aAgent,
-                 AudioChannelType aType, void* aData)
+void
+AudioChannelService::SendAudioChannelChangedNotification()
 {
-  if (aAgent) {
-    aAgent->NotifyAudioChannelStateChanged();
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return;
+  }
+
+  // Calculating the most important active channel.
+  AudioChannelType higher = AUDIO_CHANNEL_LAST;
+
+  // Top-Down in the hierarchy.
+  if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) {
+    higher = AUDIO_CHANNEL_PUBLICNOTIFICATION;
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) {
+    higher = AUDIO_CHANNEL_RINGER;
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) {
+    higher = AUDIO_CHANNEL_TELEPHONY;
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) {
+    higher = AUDIO_CHANNEL_ALARM;
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) {
+    higher = AUDIO_CHANNEL_NOTIFICATION;
   }
+
+  // Only 1 content channel hidden or a visible one.
+  else if ((!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() &&
+            !HasMoreThanOneContentChannelHidden()) ||
+           !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
+    higher = AUDIO_CHANNEL_CONTENT;
+  }
+
+  // At least 1 visible normal channel.
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) {
+    higher = AUDIO_CHANNEL_NORMAL;
+  }
+
+  if (higher != mCurrentHigherChannel) {
+    mCurrentHigherChannel = higher;
+
+    nsString channelName;
+    if (mCurrentHigherChannel != AUDIO_CHANNEL_LAST) {
+      channelName.AssignASCII(ChannelName(mCurrentHigherChannel));
+    } else {
+      channelName = NS_LITERAL_STRING("default");
+    }
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
+  }
+}
+
+PLDHashOperator
+AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
+                                      AudioChannelAgentData aData, void* aUnused)
+{
+  MOZ_ASSERT(aAgent);
+  aAgent->NotifyAudioChannelStateChanged();
   return PL_DHASH_NEXT;
 }
 
 void
 AudioChannelService::Notify()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -267,20 +296,20 @@ AudioChannelService::Notify()
   nsTArray<ContentParent*> children;
   ContentParent::GetAll(children);
   for (uint32_t i = 0; i < children.Length(); i++) {
     unused << children[i]->SendAudioChannelNotify();
   }
 }
 
 bool
-AudioChannelService::ChannelsActiveWithHigherPriorityThan(AudioChannelType aType)
+AudioChannelService::ChannelsActiveWithHigherPriorityThan(AudioChannelInternalType aType)
 {
-  for (int i = AUDIO_CHANNEL_PUBLICNOTIFICATION;
-       i != AUDIO_CHANNEL_CONTENT; --i) {
+  for (int i = AUDIO_CHANNEL_INT_PUBLICNOTIFICATION;
+       i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) {
     if (i == aType) {
       return false;
     }
 
     if (!mChannelCounters[i].IsEmpty()) {
       return true;
     }
   }
@@ -326,27 +355,65 @@ AudioChannelService::Observe(nsISupports
     NS_WARNING("ipc:content-shutdown message without property bag as subject");
     return NS_OK;
   }
 
   uint64_t childID = 0;
   nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
                                            &childID);
   if (NS_SUCCEEDED(rv)) {
-    for (int32_t type = AUDIO_CHANNEL_NORMAL;
-         type <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
+    for (int32_t type = AUDIO_CHANNEL_INT_NORMAL;
+         type < AUDIO_CHANNEL_INT_LAST;
          ++type) {
       int32_t index;
       while ((index = mChannelCounters[type].IndexOf(childID)) != -1) {
         mChannelCounters[type].RemoveElementAt(index);
       }
     }
 
     // We don't have to remove the agents from the mAgents hashtable because if
     // that table contains only agents running on the same process.
 
+    SendAudioChannelChangedNotification();
     Notify();
   } else {
     NS_WARNING("ipc:content-shutdown message without childID property");
   }
 
   return NS_OK;
 }
+
+AudioChannelService::AudioChannelInternalType
+AudioChannelService::GetInternalType(AudioChannelType aType,
+                                     bool aElementHidden)
+{
+  switch (aType) {
+    case AUDIO_CHANNEL_NORMAL:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN : AUDIO_CHANNEL_INT_NORMAL;
+
+    case AUDIO_CHANNEL_CONTENT:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN : AUDIO_CHANNEL_INT_CONTENT;
+
+    case AUDIO_CHANNEL_NOTIFICATION:
+      return AUDIO_CHANNEL_INT_NOTIFICATION;
+
+    case AUDIO_CHANNEL_ALARM:
+      return AUDIO_CHANNEL_INT_ALARM;
+
+    case AUDIO_CHANNEL_TELEPHONY:
+      return AUDIO_CHANNEL_INT_TELEPHONY;
+
+    case AUDIO_CHANNEL_RINGER:
+      return AUDIO_CHANNEL_INT_RINGER;
+
+    case AUDIO_CHANNEL_PUBLICNOTIFICATION:
+      return AUDIO_CHANNEL_INT_PUBLICNOTIFICATION;
+
+    case AUDIO_CHANNEL_LAST:
+    default:
+      break;
+  }
+
+  MOZ_NOT_REACHED();
+  return AUDIO_CHANNEL_INT_LAST;
+}
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -46,41 +46,78 @@ public:
    * Any  audio channel agent that stops playing should unregister itself to
    * this service.
    */
   virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
    * Return true if this type should be muted.
    */
-  virtual bool GetMuted(AudioChannelType aType, bool aElementHidden);
+  virtual bool GetMuted(AudioChannelAgent* aAgent, bool aElementHidden);
 
   /**
    * Return true if there is a content channel active in this process
    * or one of its subprocesses.
    */
   virtual bool ContentChannelIsActive();
 
 protected:
   void Notify();
 
+  /**
+   * Send the audio-channel-changed notification if needed.
+   */
+  void SendAudioChannelChangedNotification();
+
   /* Register/Unregister IPC types: */
   void RegisterType(AudioChannelType aType, uint64_t aChildID);
-  void UnregisterType(AudioChannelType aType, uint64_t aChildID);
+  void UnregisterType(AudioChannelType aType, bool aElementHidden,
+                      uint64_t aChildID);
+
+  bool GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
+                        bool aElementHidden, bool aElementWasHidden);
 
   AudioChannelService();
   virtual ~AudioChannelService();
 
-  bool ChannelsActiveWithHigherPriorityThan(AudioChannelType aType);
+  enum AudioChannelInternalType {
+    AUDIO_CHANNEL_INT_NORMAL = 0,
+    AUDIO_CHANNEL_INT_NORMAL_HIDDEN,
+    AUDIO_CHANNEL_INT_CONTENT,
+    AUDIO_CHANNEL_INT_CONTENT_HIDDEN,
+    AUDIO_CHANNEL_INT_NOTIFICATION,
+    AUDIO_CHANNEL_INT_ALARM,
+    AUDIO_CHANNEL_INT_TELEPHONY,
+    AUDIO_CHANNEL_INT_RINGER,
+    AUDIO_CHANNEL_INT_PUBLICNOTIFICATION,
+    AUDIO_CHANNEL_INT_LAST
+  };
+
+  bool ChannelsActiveWithHigherPriorityThan(AudioChannelInternalType aType);
+
+  bool HasMoreThanOneContentChannelHidden();
 
   const char* ChannelName(AudioChannelType aType);
 
-  nsDataHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelType > mAgents;
+  AudioChannelInternalType GetInternalType(AudioChannelType aType,
+                                           bool aElementHidden);
 
-  nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION+1];
+  struct AudioChannelAgentData {
+    AudioChannelType mType;
+    bool mElementHidden;
+    bool mMuted;
+  };
+
+  static PLDHashOperator
+  NotifyEnumerator(AudioChannelAgent* aAgent,
+                   AudioChannelAgentData aData, void *aUnused);
+
+  nsDataHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelAgentData > mAgents;
+
+  nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
 
   AudioChannelType mCurrentHigherChannel;
 
   // This is needed for IPC comunication between
   // AudioChannelServiceChild and this class.
   friend class ContentParent;
   friend class ContentChild;
 };
--- a/dom/audiochannel/AudioChannelServiceChild.cpp
+++ b/dom/audiochannel/AudioChannelServiceChild.cpp
@@ -52,23 +52,39 @@ AudioChannelServiceChild::AudioChannelSe
 {
 }
 
 AudioChannelServiceChild::~AudioChannelServiceChild()
 {
 }
 
 bool
-AudioChannelServiceChild::GetMuted(AudioChannelType aType, bool aMozHidden)
+AudioChannelServiceChild::GetMuted(AudioChannelAgent* aAgent, bool aElementHidden)
 {
+  AudioChannelAgentData data;
+  if (!mAgents.Get(aAgent, &data)) {
+    return true;
+  }
+
   ContentChild *cc = ContentChild::GetSingleton();
-  bool muted = false;
+  bool muted = true;
 
   if (cc) {
-    cc->SendAudioChannelGetMuted(aType, aMozHidden, &muted);
+    cc->SendAudioChannelGetMuted(data.mType, aElementHidden, data.mElementHidden, &muted);
+  }
+
+  // Update visibility.
+  if (data.mElementHidden != aElementHidden || data.mMuted != muted) {
+    data.mElementHidden = aElementHidden;
+    data.mMuted = muted;
+    mAgents.Put(aAgent, data);
+  }
+
+  if (cc) {
+    cc->SendAudioChannelChangedNotification();
   }
 
   return muted;
 }
 
 void
 AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                                     AudioChannelType aType)
@@ -84,25 +100,25 @@ AudioChannelServiceChild::RegisterAudioC
   if (obs) {
     obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr);
   }
 }
 
 void
 AudioChannelServiceChild::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
-  AudioChannelType type;
-  if (!mAgents.Get(aAgent, &type)) {
+  AudioChannelAgentData data;
+  if (!mAgents.Get(aAgent, &data)) {
     return;
   }
 
   AudioChannelService::UnregisterAudioChannelAgent(aAgent);
 
   ContentChild *cc = ContentChild::GetSingleton();
   if (cc) {
-    cc->SendAudioChannelUnregisterType(type);
+    cc->SendAudioChannelUnregisterType(data.mType, data.mElementHidden);
   }
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr);
   }
 }
--- a/dom/audiochannel/AudioChannelServiceChild.h
+++ b/dom/audiochannel/AudioChannelServiceChild.h
@@ -31,17 +31,17 @@ public:
 
   virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                          AudioChannelType aType);
   virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
    * Return true if this type + this mozHidden should be muted.
    */
-  virtual bool GetMuted(AudioChannelType aType, bool aMozHidden);
+  virtual bool GetMuted(AudioChannelAgent* aAgent, bool aMozHidden);
 
 protected:
   AudioChannelServiceChild();
   virtual ~AudioChannelServiceChild();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1094,46 +1094,60 @@ ContentParent::RecvFirstIdle()
     // prelaunch any sooner than this, then we'll be competing with the
     // child process and slowing it down.
     ScheduleDelayedPreallocateAppProcess();
     return true;
 }
 
 bool
 ContentParent::RecvAudioChannelGetMuted(const AudioChannelType& aType,
-                                        const bool& aMozHidden,
+                                        const bool& aElementHidden,
+                                        const bool& aElementWasHidden,
                                         bool* aValue)
 {
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     *aValue = false;
     if (service) {
-        *aValue = service->GetMuted(aType, aMozHidden);
+        *aValue = service->GetMutedInternal(aType, mChildID,
+                                            aElementHidden, aElementWasHidden);
     }
     return true;
 }
 
 bool
 ContentParent::RecvAudioChannelRegisterType(const AudioChannelType& aType)
 {
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     if (service) {
         service->RegisterType(aType, mChildID);
     }
     return true;
 }
 
 bool
-ContentParent::RecvAudioChannelUnregisterType(const AudioChannelType& aType)
+ContentParent::RecvAudioChannelUnregisterType(const AudioChannelType& aType,
+                                              const bool& aElementHidden)
 {
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     if (service) {
-        service->UnregisterType(aType, mChildID);
+        service->UnregisterType(aType, aElementHidden, mChildID);
+    }
+    return true;
+}
+
+bool
+ContentParent::RecvAudioChannelChangedNotification()
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetAudioChannelService();
+    if (service) {
+       service->SendAudioChannelChangedNotification();
     }
     return true;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(ContentParent,
                               nsIObserver,
                               nsIThreadObserver,
                               nsIDOMGeoPositionCallback)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -315,21 +315,25 @@ private:
                                  const uint32_t& aFlags,
                                  const nsCString& aCategory);
 
     virtual bool RecvPrivateDocShellsExist(const bool& aExist);
 
     virtual bool RecvFirstIdle();
 
     virtual bool RecvAudioChannelGetMuted(const AudioChannelType& aType,
-                                          const bool& aMozHidden,
+                                          const bool& aElementHidden,
+                                          const bool& aElementWasHidden,
                                           bool* aValue);
 
     virtual bool RecvAudioChannelRegisterType(const AudioChannelType& aType);
-    virtual bool RecvAudioChannelUnregisterType(const AudioChannelType& aType);
+    virtual bool RecvAudioChannelUnregisterType(const AudioChannelType& aType,
+                                                const bool& aElementHidden);
+
+    virtual bool RecvAudioChannelChangedNotification();
 
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     GeckoChildProcessHost* mSubprocess;
     ChildOSPrivileges mOSPrivileges;
 
     uint64_t mChildID;
     int32_t mGeolocationWatchID;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -428,20 +428,24 @@ parent:
 
     // Notify the parent of the presence or absence of private docshells
     PrivateDocShellsExist(bool aExist);
 
     // Tell the parent that the child has gone idle for the first time
     async FirstIdle();
 
     // Get Muted from the main AudioChannelService.
-    sync AudioChannelGetMuted(AudioChannelType aType, bool aMozHidden)
+    sync AudioChannelGetMuted(AudioChannelType aType, bool aElementHidden,
+                              bool aElementWasHidden)
         returns (bool value);
 
-    async AudioChannelRegisterType(AudioChannelType aType);
-    async AudioChannelUnregisterType(AudioChannelType aType);
+    sync AudioChannelRegisterType(AudioChannelType aType);
+    sync AudioChannelUnregisterType(AudioChannelType aType,
+                                    bool aElementHidden);
+
+    async AudioChannelChangedNotification();
 
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData);
 };
 
 }
 }