Bug 855208 - Allow one newly created content channel to play in the background. r=mchen, a=koi+
authorBruce Sun <brsun@mozilla.com>
Tue, 17 Sep 2013 15:46:06 +0800
changeset 161611 8d2350db842c576603228c29310b06ef4a48c38d
parent 161610 0926d285be960a4d0c0902a798e7850a0cfcee9f
child 161612 75b0b968f3eddabb39df13f3a0741967dcbba0c7
push id407
push userlsblakk@mozilla.com
push dateTue, 03 Dec 2013 03:32:50 +0000
treeherdermozilla-release@babf8c9ebc52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmchen, koi
bugs855208
milestone26.0a2
Bug 855208 - Allow one newly created content channel to play in the background. r=mchen, a=koi+
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/tests/TestAudioChannelService.cpp
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -67,17 +67,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)
 , 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);
 #ifdef MOZ_WIDGET_GONK
       // To monitor the volume settings based on audio channel.
@@ -120,16 +120,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
@@ -177,23 +189,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);
@@ -211,16 +222,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;
@@ -240,53 +264,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);
@@ -297,17 +284,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)) {
@@ -464,22 +460,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;
@@ -604,19 +596,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;
 
   nsCOMPtr<nsITimer> mDeferTelChannelTimer;
   bool mTimerElementHidden;
   uint64_t mTimerChildID;
 
   uint64_t mDefChannelChildID;
 
   // This is needed for IPC comunication between
--- 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);