Bug 855208: Allow one newly created content channel to play in the background. r=mchen
authorBruce Sun <brsun@mozilla.com>
Tue, 17 Sep 2013 15:46:06 +0800
changeset 164431 ec2efae11376a8b6e3386452cd187f4367e6d718
parent 164430 283c82d5fbb6512dc6a238908b2f37fa23acc7bb
child 164432 00bea129caf7016e13a7dde0e8cf245a7cba6ea4
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmchen
bugs855208
milestone27.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 855208: Allow one newly created content channel to play in the background. r=mchen
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/tests/TestAudioChannelService.cpp
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -69,17 +69,17 @@ AudioChannelService::Shutdown()
   }
 }
 
 NS_IMPL_ISUPPORTS2(AudioChannelService, nsIObserver, nsITimerCallback)
 
 AudioChannelService::AudioChannelService()
 : mCurrentHigherChannel(AUDIO_CHANNEL_LAST)
 , mCurrentVisibleHigherChannel(AUDIO_CHANNEL_LAST)
-, mActiveContentChildIDsFrozen(false)
+, mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN)
 , mDisabled(false)
 , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->AddObserver(this, "ipc:content-shutdown", false);
       obs->AddObserver(this, "xpcom-shutdown", false);
@@ -132,16 +132,28 @@ AudioChannelService::RegisterType(AudioC
       mDeferTelChannelTimer = nullptr;
       UnregisterTypeInternal(aType, mTimerElementHidden, mTimerChildID, false);
     }
 
     if (aWithVideo) {
       mWithVideoChildIDs.AppendElement(aChildID);
     }
 
+    // One hidden content channel can be playable only when there is no any
+    // content channel in the foreground.
+    if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
+        mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
+      mPlayableHiddenContentChildID = aChildID;
+    }
+    // No hidden content channel can be playable if there is an content channel
+    // in foreground.
+    else if (type == AUDIO_CHANNEL_INT_CONTENT) {
+      mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
+    }
+
     // In order to avoid race conditions, it's safer to notify any existing
     // agent any time a new one is registered.
     SendAudioChannelChangedNotification(aChildID);
     Notify();
   }
 }
 
 void
@@ -197,23 +209,22 @@ AudioChannelService::UnregisterTypeInter
   // this should remove only the first one.
   AudioChannelInternalType type = GetInternalType(aType, aElementHidden);
   MOZ_ASSERT(mChannelCounters[type].Contains(aChildID));
   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.
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
-    // We only remove ChildID when it is in the foreground.
-    // If in the background, we kept ChildID for allowing it to play next song.
+    // No hidden content channel is playable if the original playable hidden
+    // process does not need to play audio from background anymore.
     if (aType == AUDIO_CHANNEL_CONTENT &&
-        mActiveContentChildIDs.Contains(aChildID) &&
-        !aElementHidden &&
-        !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID)) {
-      mActiveContentChildIDs.RemoveElement(aChildID);
+        mPlayableHiddenContentChildID == aChildID &&
+        !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) {
+      mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
     }
 
     if (aWithVideo) {
       MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID));
       mWithVideoChildIDs.RemoveElement(aChildID);
     }
 
     SendAudioChannelChangedNotification(aChildID);
@@ -231,16 +242,29 @@ AudioChannelService::UpdateChannelType(A
   AudioChannelInternalType newType = GetInternalType(aType, aElementHidden);
   AudioChannelInternalType oldType = GetInternalType(aType, aElementWasHidden);
 
   if (newType != oldType) {
     mChannelCounters[newType].AppendElement(aChildID);
     MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID));
     mChannelCounters[oldType].RemoveElement(aChildID);
   }
+
+  // The last content channel which goes from foreground to background can also
+  // be playable.
+  if (oldType == AUDIO_CHANNEL_INT_CONTENT &&
+      newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
+      mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
+    mPlayableHiddenContentChildID = aChildID;
+  }
+  // No hidden content channel can be playable if there is an content channel
+  // in foreground.
+  else if (newType == AUDIO_CHANNEL_INT_CONTENT) {
+    mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
+  }
 }
 
 AudioChannelState
 AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden)
 {
   AudioChannelAgentData* data;
   if (!mAgents.Get(aAgent, &data)) {
     return AUDIO_CHANNEL_STATE_MUTED;
@@ -260,53 +284,16 @@ AudioChannelService::GetStateInternal(Au
                                       bool aElementHidden, bool aElementWasHidden)
 {
   UpdateChannelType(aType, aChildID, aElementHidden, aElementWasHidden);
 
   // Calculating the new and old type and update the hashtable if needed.
   AudioChannelInternalType newType = GetInternalType(aType, aElementHidden);
   AudioChannelInternalType oldType = GetInternalType(aType, aElementWasHidden);
 
-  // If the audio content channel is visible, let's remember this ChildID.
-  if (newType == AUDIO_CHANNEL_INT_CONTENT &&
-      oldType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN) {
-
-    if (mActiveContentChildIDsFrozen) {
-      mActiveContentChildIDsFrozen = false;
-      mActiveContentChildIDs.Clear();
-    }
-
-    if (!mActiveContentChildIDs.Contains(aChildID)) {
-      mActiveContentChildIDs.AppendElement(aChildID);
-    }
-  }
-  else if (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
-           oldType == AUDIO_CHANNEL_INT_CONTENT &&
-           !mActiveContentChildIDsFrozen) {
-    // If nothing is visible, the list has to been frozen.
-    // Or if there is still any one with other ChildID in foreground then
-    // it should be removed from list and left other ChildIDs in the foreground
-    // to keep playing. Finally only last one childID which go to background
-    // will be in list.
-    if (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
-      mActiveContentChildIDsFrozen = true;
-    } else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID)) {
-      MOZ_ASSERT(mActiveContentChildIDs.Contains(aChildID));
-      mActiveContentChildIDs.RemoveElement(aChildID);
-    }
-  }
-  else if (newType == AUDIO_CHANNEL_INT_NORMAL &&
-           oldType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN &&
-           mWithVideoChildIDs.Contains(aChildID)) {
-    if (mActiveContentChildIDsFrozen) {
-      mActiveContentChildIDsFrozen = false;
-      mActiveContentChildIDs.Clear();
-    }
-  }
-
   if (newType != oldType &&
       (aType == AUDIO_CHANNEL_CONTENT ||
        (aType == AUDIO_CHANNEL_NORMAL &&
         mWithVideoChildIDs.Contains(aChildID)))) {
     Notify();
   }
 
   SendAudioChannelChangedNotification(aChildID);
@@ -317,17 +304,26 @@ AudioChannelService::GetStateInternal(Au
       return AUDIO_CHANNEL_STATE_FADED;
     }
     return AUDIO_CHANNEL_STATE_NORMAL;
   }
 
   // We are not visible, maybe we have to mute.
   if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
       (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
-       !mActiveContentChildIDs.Contains(aChildID))) {
+       // One process can have multiple content channels; and during the
+       // transition from foreground to background, its content channels will be
+       // updated with correct visibility status one by one. All its content
+       // channels should remain playable until all of their visibility statuses
+       // have been updated as hidden. After all its content channels have been
+       // updated properly as hidden, mPlayableHiddenContentChildID is used to
+       // check whether this background process is playable or not.
+       !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
+         (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() &&
+          mPlayableHiddenContentChildID == aChildID)))) {
     return AUDIO_CHANNEL_STATE_MUTED;
   }
 
   // After checking the condition on normal & content channel, if the state
   // is not on muted then checking other higher channels type here.
   if (ChannelsActiveWithHigherPriorityThan(newType)) {
     MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
     if (CheckVolumeFadedCondition(newType, aElementHidden)) {
@@ -488,22 +484,18 @@ AudioChannelService::SendAudioChannelCha
     else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM_HIDDEN].IsEmpty()) {
       higher = AUDIO_CHANNEL_ALARM;
     }
 
     else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) {
       higher = AUDIO_CHANNEL_NOTIFICATION;
     }
 
-    // There is only one Child can play content channel in the background.
-    // And need to check whether there is any content channels under playing
-    // now.
-    else if (!mActiveContentChildIDs.IsEmpty() &&
-             mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(
-             mActiveContentChildIDs[0])) {
+    // Check whether there is any playable hidden content channel or not.
+    else if (mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) {
       higher = AUDIO_CHANNEL_CONTENT;
     }
   }
 
   if (higher != mCurrentHigherChannel) {
     mCurrentHigherChannel = higher;
 
     nsString channelName;
@@ -636,19 +628,22 @@ AudioChannelService::Observe(nsISupports
            type < AUDIO_CHANNEL_INT_LAST;
            ++type) {
 
         while ((index = mChannelCounters[type].IndexOf(childID)) != -1) {
           mChannelCounters[type].RemoveElementAt(index);
         }
       }
 
-      while ((index = mActiveContentChildIDs.IndexOf(childID)) != -1) {
-        mActiveContentChildIDs.RemoveElementAt(index);
+      // No hidden content channel is playable if the original playable hidden
+      // process shuts down.
+      if (mPlayableHiddenContentChildID == childID) {
+        mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
       }
+
       while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) {
         mWithVideoChildIDs.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(childID);
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -164,19 +164,36 @@ protected:
 
   nsClassHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelAgentData > mAgents;
 
   nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
 
   AudioChannelType mCurrentHigherChannel;
   AudioChannelType mCurrentVisibleHigherChannel;
 
-  nsTArray<uint64_t> mActiveContentChildIDs;
   nsTArray<uint64_t> mWithVideoChildIDs;
-  bool mActiveContentChildIDsFrozen;
+
+  // mPlayableHiddenContentChildID stores the ChildID of the process which can
+  // play content channel(s) in the background.
+  // A background process contained content channel(s) will become playable:
+  //   1. When this background process registers its content channel(s) in
+  //   AudioChannelService and there is no foreground process with registered
+  //   content channel(s).
+  //   2. When this process goes from foreground into background and there is
+  //   no foreground process with registered content channel(s).
+  // A background process contained content channel(s) will become non-playable:
+  //   1. When there is a foreground process registering its content channel(s)
+  //   in AudioChannelService.
+  //   ps. Currently this condition is never satisfied because the default value
+  //   of visibility status of each channel during registering is hidden = true.
+  //   2. When there is a process with registered content channel(s) goes from
+  //   background into foreground.
+  //   3. When this process unregisters all hidden content channels.
+  //   4. When this process shuts down.
+  uint64_t mPlayableHiddenContentChildID;
 
   bool mDisabled;
 
   nsCOMPtr<nsITimer> mDeferTelChannelTimer;
   bool mTimerElementHidden;
   uint64_t mTimerChildID;
 
   uint64_t mDefChannelChildID;
--- a/dom/audiochannel/tests/TestAudioChannelService.cpp
+++ b/dom/audiochannel/tests/TestAudioChannelService.cpp
@@ -267,33 +267,33 @@ TestContentChannels()
   NS_ENSURE_SUCCESS(rv, rv);
   rv = agent2->SetVisibilityState(true);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = agent1->StopPlaying();
   NS_ENSURE_SUCCESS(rv, rv);
   rv = agent2->StopPlaying();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Test that content channels can't be allow to play when they starts from
+  // Test that content channels can be allow to play when they starts from
   // the background state
   rv = agent1->SetVisibilityState(false);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = agent2->SetVisibilityState(false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = agent1->StartPlaying(&playable);
   NS_ENSURE_SUCCESS(rv, rv);
-  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
-    "Test3: A content channel unvisible agent1 must be muted while playing "
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel unvisible agent1 must be playable "
     "from background state");
 
   rv = agent2->StartPlaying(&playable);
   NS_ENSURE_SUCCESS(rv, rv);
-  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
-    "Test3: A content channel unvisible agent2 must be muted while playing "
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel unvisible agent2 must be playable "
     "from background state");
 
   agent1->StopPlaying();
   agent2->StopPlaying();
   return rv;
 }
 
 nsresult
@@ -413,18 +413,18 @@ TestPriorities()
 
   rv = normalAgent->StartPlaying(&playable);
   NS_ENSURE_SUCCESS(rv, rv);
   TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
     "Test5: A normal channel unvisible agent must be muted");
 
   rv = contentAgent->StartPlaying(&playable);
   NS_ENSURE_SUCCESS(rv, rv);
-  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
-    "Test5: A content channel unvisible agent agent must be muted while "
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A content channel unvisible agent must be playable while "
     "playing from background state");
 
   rv = notificationAgent->StartPlaying(&playable);
   NS_ENSURE_SUCCESS(rv, rv);
   TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
     "Test5: A notification channel unvisible agent must be playable");
 
   rv = alarmAgent->StartPlaying(&playable);