Bug 1339230 - Only need to do audio competing for active agent. r=baku, a=jcristau
authorAlastor Wu <alwu@mozilla.com>
Wed, 01 Mar 2017 11:22:34 +0800
changeset 376518 1d2d7278570a8c83c4d8d327f0707f9619305209
parent 376517 a3fe876dacfbd1f5610df0df020d295626b2d4dd
child 376519 0b67d410dd2d57d0745f7990671aba6ff9adcc45
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, jcristau
bugs1339230
milestone53.0a2
Bug 1339230 - Only need to do audio competing for active agent. r=baku, a=jcristau
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/html/HTMLMediaElement.cpp
modules/libpref/init/all.js
toolkit/content/tests/browser/browser.ini
toolkit/content/tests/browser/browser_audioCompeting_onlyForActiveAgent.js
toolkit/content/tests/browser/file_multiplePlayingAudio.html
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -36,16 +36,17 @@ using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
 namespace {
 
 // If true, any new AudioChannelAgent will be muted when created.
 bool sAudioChannelMutedByDefault = false;
 bool sAudioChannelCompeting = false;
+bool sAudioChannelCompetingAllAgents = false;
 bool sXPCOMShuttingDown = false;
 
 class NotifyChannelActiveRunnable final : public Runnable
 {
 public:
   NotifyChannelActiveRunnable(uint64_t aWindowID, AudioChannel aAudioChannel,
                               bool aActive)
     : mWindowID(aWindowID)
@@ -165,17 +166,17 @@ IsEnableAudioCompetingForAllAgents()
   // helps user can focus on one media at the same time. However, we hope to
   // treat all media as the same in the mobile device. First reason is we have
   // media control on fennec and we just want to control one media at once time.
   // Second reason is to reduce the bandwidth, avoiding to play any non-audible
   // media in background which user doesn't notice about.
 #ifdef MOZ_WIDGET_ANDROID
   return true;
 #else
-  return false;
+  return sAudioChannelCompetingAllAgents;
 #endif
 }
 
 } // anonymous namespace
 
 StaticRefPtr<AudioChannelService> gAudioChannelService;
 
 // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
@@ -286,16 +287,18 @@ AudioChannelService::AudioChannelService
       obs->AddObserver(this, "ipc:content-shutdown", false);
     }
   }
 
   Preferences::AddBoolVarCache(&sAudioChannelMutedByDefault,
                                "dom.audiochannel.mutedByDefault");
   Preferences::AddBoolVarCache(&sAudioChannelCompeting,
                                "dom.audiochannel.audioCompeting");
+  Preferences::AddBoolVarCache(&sAudioChannelCompetingAllAgents,
+                               "dom.audiochannel.audioCompeting.allAgents");
 }
 
 AudioChannelService::~AudioChannelService()
 {
 }
 
 void
 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
@@ -1061,27 +1064,26 @@ AudioChannelService::ChildStatusReceived
     mPlayingChildren.AppendElement(data);
   }
 
   data->mActiveTelephonyChannel = aTelephonyChannel;
   data->mActiveContentOrNormalChannel = aContentOrNormalChannel;
 }
 
 void
-AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent,
-                                                    bool aActive)
+AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
 
   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
     iter(mWindows);
   while (iter.HasMore()) {
     AudioChannelWindow* winData = iter.GetNext();
     if (winData->mOwningAudioFocus) {
-      winData->AudioFocusChanged(aAgent, aActive);
+      winData->AudioFocusChanged(aAgent);
     }
   }
 }
 
 void
 AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
@@ -1107,18 +1109,17 @@ AudioChannelService::AudioChannelWindow:
 
   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
          ("AudioChannelWindow, RequestAudioFocus, this = %p, "
           "agent = %p, owning audio focus = %d\n",
           this, aAgent, mOwningAudioFocus));
 }
 
 void
-AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent,
-                                                                     bool aActive)
+AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent)
 {
   // This function may be called after RemoveAgentAndReduceAgentsNum(), so the
   // agent may be not contained in mAgent. In addition, the agent would still
   // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
   MOZ_ASSERT(aAgent);
 
   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   MOZ_ASSERT(service);
@@ -1128,20 +1129,20 @@ AudioChannelService::AudioChannelWindow:
   }
 
   if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
     return;
   }
 
   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
          ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
-          "agent = %p, active = %d\n",
-          this, aAgent, aActive));
+          "agent = %p\n",
+          this, aAgent));
 
-  service->RefreshAgentsAudioFocusChanged(aAgent, aActive);
+  service->RefreshAgentsAudioFocusChanged(aAgent);
 }
 
 bool
 AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const
 {
   MOZ_ASSERT(aAgent);
 
   if(!mOwningAudioFocus) {
@@ -1161,18 +1162,17 @@ bool
 AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const
 {
   bool hasMultipleActiveAgents = IsEnableAudioCompetingForAllAgents() ?
     mAgents.Length() > 1 : mAudibleAgents.Length() > 1;
   return mOwningAudioFocus && hasMultipleActiveAgents;
 }
 
 void
-AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent,
-                                                           bool aActive)
+AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent)
 {
   // This agent isn't always known for the current window, because it can comes
   // from other window.
   MOZ_ASSERT(aNewPlayingAgent);
 
   if (IsInactiveWindow()) {
     // These would happen in two situations,
     // (1) Audio in page A was ended, and another page B want to play audio.
@@ -1187,18 +1187,17 @@ AudioChannelService::AudioChannelWindow:
       MOZ_ASSERT(agent);
 
       // Don't need to update the playing state of new playing agent.
       if (agent == aNewPlayingAgent) {
         continue;
       }
 
       uint32_t type = GetCompetingBehavior(agent,
-                                           aNewPlayingAgent->AudioChannelType(),
-                                           aActive);
+                                           aNewPlayingAgent->AudioChannelType());
 
       // If window will be suspended, it needs to abandon the audio focus
       // because only one window can own audio focus at a time. However, we
       // would support multiple audio focus at the same time in the future.
       mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
 
       // TODO : support other behaviors which are definded in MediaSession API.
       switch (type) {
@@ -1218,30 +1217,28 @@ AudioChannelService::AudioChannelWindow:
 bool
 AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const
 {
   return (aAgent->WindowID() == mWindowID);
 }
 
 uint32_t
 AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent,
-                                                              int32_t aIncomingChannelType,
-                                                              bool aIncomingChannelActive) const
+                                                              int32_t aIncomingChannelType) const
 {
   MOZ_ASSERT(aAgent);
   MOZ_ASSERT(IsEnableAudioCompetingForAllAgents() ?
     mAgents.Contains(aAgent) : mAudibleAgents.Contains(aAgent));
 
   uint32_t competingBehavior = nsISuspendedTypes::NONE_SUSPENDED;
   int32_t presentChannelType = aAgent->AudioChannelType();
 
   // TODO : add other competing cases for MediaSession API
   if (presentChannelType == int32_t(AudioChannel::Normal) &&
-      aIncomingChannelType == int32_t(AudioChannel::Normal) &&
-      aIncomingChannelActive) {
+      aIncomingChannelType == int32_t(AudioChannel::Normal)) {
     competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
   }
 
   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
          ("AudioChannelWindow, GetCompetingBehavior, this = %p, "
           "present type = %d, incoming channel = %d, behavior = %d\n",
           this, presentChannelType, aIncomingChannelType, competingBehavior));
 
@@ -1265,17 +1262,17 @@ AudioChannelService::AudioChannelWindow:
   AppendAgentAndIncreaseAgentsNum(aAgent);
   AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
   if (aAudible == AudibleState::eAudible) {
     AudioAudibleChanged(aAgent,
                         AudibleState::eAudible,
                         AudibleChangedReasons::eDataAudibleChanged);
   } else if (IsEnableAudioCompetingForAllAgents() &&
              aAudible != AudibleState::eAudible) {
-    NotifyAudioCompetingChanged(aAgent, true);
+    NotifyAudioCompetingChanged(aAgent);
   }
 }
 
 void
 AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
 
@@ -1343,18 +1340,19 @@ AudioChannelService::AudioChannelWindow:
   MOZ_ASSERT(aAgent);
 
   if (aAudible == AudibleState::eAudible) {
     AppendAudibleAgentIfNotContained(aAgent, aReason);
   } else {
     RemoveAudibleAgentIfContained(aAgent, aReason);
   }
 
-  NotifyAudioCompetingChanged(aAgent, aAudible == AudibleState::eAudible);
-  if (aAudible != AudibleState::eNotAudible) {
+  if (aAudible == AudibleState::eAudible) {
+    NotifyAudioCompetingChanged(aAgent);
+  } else if (aAudible != AudibleState::eNotAudible) {
     MaybeNotifyMediaBlocked(aAgent);
   }
 }
 
 void
 AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
                                                                           AudibleChangedReasons aReason)
 {
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -222,18 +222,17 @@ private:
   void MaybeSendStatusUpdate();
 
   bool ContentOrNormalChannelIsActive();
 
   /* Send the default-volume-channel-changed notification */
   void SetDefaultVolumeControlChannelInternal(int32_t aChannel,
                                               bool aVisible, uint64_t aChildID);
 
-  void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent,
-                                      bool aActive);
+  void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent);
 
   class AudioChannelConfig final : public AudioPlaybackConfig
   {
   public:
     AudioChannelConfig()
       : AudioPlaybackConfig(1.0, IsAudioChannelMutedByDefault(),
                             nsISuspendedTypes::NONE_SUSPENDED)
       , mNumberOfAgents(0)
@@ -249,17 +248,17 @@ private:
       : mWindowID(aWindowID)
       , mIsAudioCaptured(false)
       , mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting())
     {
       // Workaround for bug1183033, system channel type can always playback.
       mChannels[(int16_t)AudioChannel::System].mMuted = false;
     }
 
-    void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent, bool aActive);
+    void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent);
     void AudioAudibleChanged(AudioChannelAgent* aAgent,
                              AudibleState aAudible,
                              AudibleChangedReasons aReason);
 
     void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible);
     void RemoveAgent(AudioChannelAgent* aAgent);
 
     uint64_t mWindowID;
@@ -293,21 +292,21 @@ private:
                                    AudibleState aAudible,
                                    AudibleChangedReasons aReason);
 
     void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel,
                              bool aActive);
     void MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent);
 
     void RequestAudioFocus(AudioChannelAgent* aAgent);
-    void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent, bool aActive);
+    // We need to do audio competing only when the new incoming agent started.
+    void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent);
 
     uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent,
-                                  int32_t aIncomingChannelType,
-                                  bool aIncomingChannelActive) const;
+                                  int32_t aIncomingChannelType) const;
     bool IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const;
     bool IsAudioCompetingInSameTab() const;
     bool IsContainingPlayingAgent(AudioChannelAgent* aAgent) const;
 
     bool IsInactiveWindow() const;
   };
 
   AudioChannelWindow*
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1010,16 +1010,21 @@ private:
       return AudioChannelService::AudibleState::eNotAudible;
     }
 
     // Might be audible but not yet.
     if (mOwner->HasAudio() && !mOwner->mIsAudioTrackAudible) {
       return AudioChannelService::AudibleState::eMaybeAudible;
     }
 
+    // Media is suspended.
+    if (mSuspended != nsISuspendedTypes::NONE_SUSPENDED) {
+      return AudioChannelService::AudibleState::eNotAudible;
+    }
+
     return AudioChannelService::AudibleState::eAudible;
   }
 
   bool
   IsPlayingThroughTheAudioChannel() const
   {
     // If we have an error, we are not playing.
     if (mOwner->GetError()) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5510,16 +5510,17 @@ pref("plugins.navigator_hide_disabled_fl
 
 // Disable browser frames by default
 pref("dom.mozBrowserFramesEnabled", false);
 
 // Is support for 'color-adjust' CSS property enabled?
 pref("layout.css.color-adjust.enabled", true);
 
 pref("dom.audiochannel.audioCompeting", false);
+pref("dom.audiochannel.audioCompeting.allAgents", false);
 
 // Default media volume
 pref("media.default_volume", "1.0");
 
 // Once bug 1276272 is resolved, we will trun this preference to default ON in
 // non-release channels.
 #ifdef RELEASE_OR_BETA
 pref("media.seekToNextFrame.enabled", false);
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -3,16 +3,20 @@ support-files =
   head.js
   file_contentTitle.html
   audio.ogg
 
 [browser_audioCompeting.js]
 tags = audiochannel
 support-files =
   file_multipleAudio.html
+[browser_audioCompeting_onlyForActiveAgent.js]
+tags = audiochannel
+support-files =
+  file_multiplePlayingAudio.html
 [browser_autoscroll_disabled.js]
 [browser_block_autoplay_media.js]
 tags = audiochannel
 support-files =
   file_multipleAudio.html
 [browser_block_autoplay_media_pausedAfterPlay.js]
  tags = audiochannel
 support-files =
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_audioCompeting_onlyForActiveAgent.js
@@ -0,0 +1,176 @@
+const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multiplePlayingAudio.html";
+
+var SuspendedType = {
+  NONE_SUSPENDED             : 0,
+  SUSPENDED_PAUSE            : 1,
+  SUSPENDED_BLOCK            : 2,
+  SUSPENDED_PAUSE_DISPOSABLE : 3
+};
+
+function wait_for_event(browser, event) {
+  return BrowserTestUtils.waitForEvent(browser, event, false, (event) => {
+    is(event.originalTarget, browser, "Event must be dispatched to correct browser.");
+    return true;
+  });
+}
+
+function check_all_audio_suspended(suspendedType) {
+  var audio1 = content.document.getElementById("audio1");
+  var audio2 = content.document.getElementById("audio2");
+  if (!audio1 || !audio2) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio1.computedSuspended, suspendedType,
+     "The suspeded state of audio1 is correct.");
+  is(audio2.computedSuspended, suspendedType,
+     "The suspeded state of audio2 is correct.");
+}
+
+function check_audio1_suspended(suspendedType) {
+  var audio1 = content.document.getElementById("audio1");
+  if (!audio1) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio1.computedSuspended, suspendedType,
+     "The suspeded state of audio1 is correct.");
+}
+
+function check_audio2_suspended(suspendedType) {
+  var audio2 = content.document.getElementById("audio2");
+  if (!audio2) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio2.computedSuspended, suspendedType,
+     "The suspeded state of audio2 is correct.");
+}
+
+function check_all_audio_pause_state(expectedPauseState) {
+  var audio1 = content.document.getElementById("audio1");
+  var audio2 = content.document.getElementById("audio2");
+  if (!audio1 | !audio2) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio1.paused, expectedPauseState,
+    "The pause state of audio1 is correct.");
+  is(audio2.paused, expectedPauseState,
+    "The pause state of audio2 is correct.");
+}
+
+function check_audio1_pause_state(expectedPauseState) {
+  var audio1 = content.document.getElementById("audio1");
+  if (!audio1) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio1.paused, expectedPauseState,
+    "The pause state of audio1 is correct.");
+}
+
+function check_audio2_pause_state(expectedPauseState) {
+  var audio2 = content.document.getElementById("audio2");
+  if (!audio2) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio2.paused, expectedPauseState,
+    "The pause state of audio2 is correct.");
+}
+
+function play_audio1_from_page() {
+  var audio1 = content.document.getElementById("audio1");
+  if (!audio1) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio1.paused, true, "Audio1 is paused.");
+  audio1.play();
+  return new Promise(resolve => {
+    audio1.onplay = function() {
+      audio1.onplay = null;
+      ok(true, "Audio1 started playing.");
+      resolve();
+    }
+  });
+}
+
+function stop_audio1_from_page() {
+  var audio1 = content.document.getElementById("audio1");
+  if (!audio1) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio1.paused, false, "Audio1 is playing.");
+  audio1.pause();
+  return new Promise(resolve => {
+    audio1.onpause = function() {
+      audio1.onpause = null;
+      ok(true, "Audio1 stopped playing.");
+      resolve();
+    }
+  });
+}
+
+function* audio_competing_for_active_agent(url, browser) {
+  browser.loadURI(url);
+
+  info("- page should have playing audio -");
+  yield wait_for_event(browser, "DOMAudioPlaybackStarted");
+
+  info("- the default suspended state of all audio should be non-suspened -");
+  yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED,
+                                   check_all_audio_suspended);
+
+  info("- only pause playing audio in the page -");
+  browser.pauseMedia(true /* disposable */);
+
+  info("- page shouldn't have any playing audio -");
+  yield wait_for_event(browser, "DOMAudioPlaybackStopped");
+  yield ContentTask.spawn(browser, true /* expect for pause */,
+                                   check_all_audio_pause_state);
+  yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE,
+                                   check_all_audio_suspended);
+
+  info("- resume audio1 from page -");
+  yield ContentTask.spawn(browser, null,
+                                   play_audio1_from_page);
+  yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED,
+                                   check_audio1_suspended);
+
+  info("- audio2 should still be suspended -");
+  yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE,
+                                   check_audio2_suspended);
+  yield ContentTask.spawn(browser, true /* expect for pause */,
+                                   check_audio2_pause_state);
+
+  info("- stop audio1 from page -");
+  yield ContentTask.spawn(browser, null,
+                                   stop_audio1_from_page);
+  yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED,
+                                   check_audio1_suspended);
+
+  info("- audio2 should still be suspended -");
+  yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE,
+                                   check_audio2_suspended);
+  yield ContentTask.spawn(browser, true /* expect for pause */,
+                                   check_audio2_pause_state);
+
+}
+
+add_task(function* setup_test_preference() {
+  yield SpecialPowers.pushPrefEnv({"set": [
+    ["media.useAudioChannelService.testing", true],
+    ["dom.audiochannel.audioCompeting", true],
+    ["dom.audiochannel.audioCompeting.allAgents", true]
+  ]});
+});
+
+add_task(function* test_suspended_pause_disposable() {
+  yield BrowserTestUtils.withNewTab({
+      gBrowser,
+      url: "about:blank"
+    }, audio_competing_for_active_agent.bind(this, PAGE));
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_multiplePlayingAudio.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+  <meta content="utf-8" http-equiv="encoding">
+</head>
+<body>
+<audio id="audio1" src="audio.ogg" controls></audio>
+<audio id="audio2" src="audio.ogg" controls></audio>
+<script type="text/javascript">
+
+// In linux debug on try server, sometimes the download process would fail, so
+// we can't activate the "auto-play" or playing after receving "oncanplay".
+// Therefore, we just call play here.
+var audio1 = document.getElementById("audio1");
+audio1.loop = true;
+audio1.play();
+
+var audio2 = document.getElementById("audio2");
+audio2.loop = true;
+audio2.play();
+
+</script>
+</body>