Bug 1242874 - part3 : implement different suspended methods. draft
authorAlastor Wu <alwu@mozilla.com>
Thu, 21 Apr 2016 17:16:28 +0800
changeset 354582 9493cfcfb3fcf593bc88023e6a0bc0b9dc50d9cd
parent 354581 0f755dbf0c68ca1df2a6c43f0cbfee464bb16412
child 354583 f4da87dc7e3f59ff1f4466a0d54cd90ed3a89b3a
push id16130
push useralwu@mozilla.com
push dateThu, 21 Apr 2016 10:15:27 +0000
bugs1242874
milestone48.0a1
Bug 1242874 - part3 : implement different suspended methods. MozReview-Commit-ID: CQSnGzpVH2J
dom/audiochannel/AudioChannelAgent.cpp
dom/audiochannel/AudioChannelAgent.h
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/base/nsGlobalWindow.cpp
dom/fmradio/FMRadio.cpp
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/webaudio/AudioDestinationNode.cpp
dom/media/webaudio/AudioDestinationNode.h
dom/media/webspeech/synth/nsSpeechTask.cpp
dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/webidl/HTMLMediaElement.webidl
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -218,21 +218,26 @@ NS_IMETHODIMP AudioChannelAgent::NotifyS
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
       service == nullptr || mIsRegToService) {
     return NS_ERROR_FAILURE;
   }
 
   service->RegisterAudioChannelAgent(this,
     static_cast<AudioChannel>(mAudioChannelType));
 
-  service->GetState(mWindow, mAudioChannelType, aVolume, aMuted);
+  WindowMediaConfig config = service->GetMediaConfig(mWindow,
+                                                     mAudioChannelType);
 
   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-         ("AudioChannelAgent, NotifyStartedPlaying, this = %p, mute = %d, "
-          "volume = %f\n", this, *aMuted, *aVolume));
+         ("AudioChannelAgent, NotifyStartedPlaying, this = %p, "
+          "mute = %d, volume = %f, suspended = %d\n", this,
+          config.mMuted, config.mVolume, config.mSuspended));
+
+  *aVolume = config.mVolume;
+  *aMuted = config.mMuted;
 
   mIsRegToService = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying()
 {
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
@@ -265,29 +270,59 @@ AudioChannelAgent::GetCallback()
 void
 AudioChannelAgent::WindowVolumeChanged()
 {
   nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
   if (!callback) {
     return;
   }
 
-  float volume = 1.0;
-  bool muted = false;
+  WindowMediaConfig config = GetMediaConfig();
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %d, "
+          "volume = %f\n", this, config.mMuted, config.mVolume));
+
+  callback->WindowVolumeChanged(config.mVolume, config.mMuted);
+}
 
-  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-  if (service) {
-    service->GetState(mWindow, mAudioChannelType, &volume, &muted);
+void
+AudioChannelAgent::WindowSuspendedChanged(nsSuspendedTypes aSuspended)
+{
+  nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+  if (!callback) {
+    return;
+  }
+
+  if (!IsDisposableSuspended(aSuspended)) {
+    aSuspended = GetMediaConfig().mSuspended;
   }
 
   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-         ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %d, "
-          "volume = %f\n", this, muted, volume));
+         ("AudioChannelAgent, WindowSuspendedChanged, this = %p, "
+          "suspended = %d\n", this, aSuspended));
+
+  callback->WindowSuspendedChanged(aSuspended);
+}
 
-  callback->WindowVolumeChanged(volume, muted);
+WindowMediaConfig
+AudioChannelAgent::GetMediaConfig()
+{
+  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  WindowMediaConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
+  if (service) {
+    config = service->GetMediaConfig(mWindow, mAudioChannelType);
+  }
+  return config;
+}
+
+bool
+AudioChannelAgent::IsDisposableSuspended(nsSuspendedTypes aSuspended) const
+{
+  return (aSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
+          aSuspended == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
 }
 
 uint64_t
 AudioChannelAgent::WindowID() const
 {
   return mWindow ? mWindow->WindowID() : 0;
 }
 
--- a/dom/audiochannel/AudioChannelAgent.h
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -18,41 +18,47 @@
       {0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}}
 
 class nsPIDOMWindowInner;
 class nsPIDOMWindowOuter;
 
 namespace mozilla {
 namespace dom {
 
+class WindowMediaConfig;
+
 /* Header file */
 class AudioChannelAgent : public nsIAudioChannelAgent
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIAUDIOCHANNELAGENT
 
   NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
 
   AudioChannelAgent();
 
   void WindowVolumeChanged();
+  void WindowSuspendedChanged(nsSuspendedTypes aSuspended);
   void WindowAudioCaptureChanged(uint64_t aInnerWindowID, bool aCapture);
 
   nsPIDOMWindowOuter* Window() const
   {
     return mWindow;
   }
 
   uint64_t WindowID() const;
   uint64_t InnerWindowID() const;
 
 private:
   virtual ~AudioChannelAgent();
 
+  WindowMediaConfig GetMediaConfig();
+  bool IsDisposableSuspended(nsSuspendedTypes aSuspended) const;
+
   // Returns mCallback if that's non-null, or otherwise tries to get an
   // nsIAudioChannelAgentCallback out of mWeakCallback.
   already_AddRefed<nsIAudioChannelAgentCallback> GetCallback();
 
   nsresult InitInternal(nsPIDOMWindowInner* aWindow, int32_t aAudioAgentType,
                         nsIAudioChannelAgentCallback* aCallback,
                         bool aUseWeakRef);
 
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -348,59 +348,63 @@ AudioChannelService::RegisterTabParent(T
 
 void
 AudioChannelService::UnregisterTabParent(TabParent* aTabParent)
 {
   MOZ_ASSERT(aTabParent);
   mTabParents.RemoveElement(aTabParent);
 }
 
-void
-AudioChannelService::GetState(nsPIDOMWindowOuter* aWindow, uint32_t aAudioChannel,
-                              float* aVolume, bool* aMuted)
+WindowMediaConfig
+AudioChannelService::GetMediaConfig(nsPIDOMWindowOuter* aWindow,
+                                    uint32_t aAudioChannel) const
 {
   MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow());
-  MOZ_ASSERT(aVolume && aMuted);
   MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS);
 
+  WindowMediaConfig config(1.0, false,
+                           nsISuspendedTypes::NONE_SUSPENDED);
+
 
   if (!aWindow || !aWindow->IsOuterWindow()) {
-    *aVolume = 0.0;
-    *aMuted = true;
-    return;
+    config.SetConfig(0.0, true,
+                     nsISuspendedTypes::SUSPENDED_BLOCK);
+    return config;
   }
 
-  *aVolume = 1.0;
-  *aMuted = false;
-
   AudioChannelWindow* winData = nullptr;
   nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
 
   // The volume must be calculated based on the window hierarchy. Here we go up
   // to the top window and we calculate the volume and the muted flag.
   do {
     winData = GetWindowData(window->WindowID());
     if (winData) {
-      *aVolume *= winData->mChannels[aAudioChannel].mVolume;
-      *aMuted = *aMuted || winData->mChannels[aAudioChannel].mMuted;
+      config.mVolume *= winData->mChannels[aAudioChannel].mVolume;
+      config.mMuted = config.mMuted || winData->mChannels[aAudioChannel].mMuted;
     }
 
-    *aVolume *= window->GetAudioVolume();
-    // TODO : distiguish between suspend and mute, it would be done in bug1242874.
-    *aMuted = *aMuted || window->GetMediaSuspended() || window->GetAudioMuted();
+    config.mVolume *= window->GetAudioVolume();
+    config.mMuted = config.mMuted || window->GetAudioMuted();
+
+    // If the mSuspended is already suspended, we don't need to set it again.
+    config.mSuspended = (config.mSuspended == nsISuspendedTypes::NONE_SUSPENDED) ?
+      window->GetMediaSuspended() : config.mSuspended;
 
     nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParent();
     if (window == win) {
       break;
     }
 
     window = do_QueryInterface(win);
 
     // If there is no parent, or we are the toplevel we don't continue.
   } while (window && window != aWindow);
+
+  return config;
 }
 
 bool
 AudioChannelService::TelephonyChannelIsActive()
 {
   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows);
   while (windowsIter.HasMore()) {
     AudioChannelWindow* next = windowsIter.GetNext();
@@ -595,17 +599,18 @@ AudioChannelService::RefreshAgentsVolume
                                                    winData->mChannels[(uint32_t)aAudioChannel].mVolume,
                                                    winData->mChannels[(uint32_t)aAudioChannel].mMuted);
   }
 
   RefreshAgentsVolume(aWindow);
 }
 
 void
-AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow)
+AudioChannelService::RefreshAgents(nsPIDOMWindowOuter* aWindow,
+                                   mozilla::function<void(AudioChannelAgent*)> aFunc)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aWindow->IsOuterWindow());
 
   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
   if (!topWindow) {
     return;
   }
@@ -613,21 +618,38 @@ AudioChannelService::RefreshAgentsVolume
   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
   if (!winData) {
     return;
   }
 
   nsTObserverArray<AudioChannelAgent*>::ForwardIterator
     iter(winData->mAgents);
   while (iter.HasMore()) {
-    iter.GetNext()->WindowVolumeChanged();
+    aFunc(iter.GetNext());
   }
 }
 
 void
+AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow)
+{
+  RefreshAgents(aWindow, [] (AudioChannelAgent* agent) {
+    agent->WindowVolumeChanged();
+  });
+}
+
+void
+AudioChannelService::RefreshAgentsSuspended(nsPIDOMWindowOuter* aWindow,
+                                            nsSuspendedTypes aSuspended)
+{
+  RefreshAgents(aWindow, [aSuspended] (AudioChannelAgent* agent) {
+    agent->WindowSuspendedChanged(aSuspended);
+  });
+}
+
+void
 AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
                                             uint64_t aInnerWindowID,
                                             bool aCapture)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aWindow->IsOuterWindow());
 
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -11,32 +11,55 @@
 #include "nsAutoPtr.h"
 #include "nsIObserver.h"
 #include "nsTObserverArray.h"
 #include "nsTArray.h"
 
 #include "AudioChannelAgent.h"
 #include "nsAttrValue.h"
 #include "mozilla/dom/AudioChannelBinding.h"
+#include "mozilla/Function.h"
 
 class nsIRunnable;
 class nsPIDOMWindowOuter;
 struct PRLogModuleInfo;
 
 namespace mozilla {
 namespace dom {
 
 #ifdef MOZ_WIDGET_GONK
 class SpeakerManagerService;
 #endif
 
 class TabParent;
 
 #define NUMBER_OF_AUDIO_CHANNELS (uint32_t)AudioChannel::EndGuard_
 
+class WindowMediaConfig
+{
+public:
+  explicit WindowMediaConfig(float aVolume, bool aMuted,
+                             uint32_t aSuspended)
+    : mVolume(aVolume)
+    , mMuted(aMuted)
+    , mSuspended(aSuspended)
+  {}
+
+  void SetConfig(float aVolume, bool aMuted, uint32_t aSuspended)
+  {
+    mVolume = aVolume;
+    mMuted = aMuted;
+    mSuspended = aSuspended;
+  }
+
+  float mVolume;
+  bool mMuted;
+  uint32_t mSuspended;
+};
+
 class AudioChannelService final : public nsIAudioChannelService
                                 , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIAUDIOCHANNELSERVICE
 
@@ -67,20 +90,20 @@ public:
   /**
    * For nested iframes.
    */
   void RegisterTabParent(TabParent* aTabParent);
   void UnregisterTabParent(TabParent* aTabParent);
 
   /**
    * Return the state to indicate this audioChannel for his window should keep
-   * playing/muted.
+   * playing/muted/suspended.
    */
-  void GetState(nsPIDOMWindowOuter* aWindow, uint32_t aChannel,
-                float* aVolume, bool* aMuted);
+  WindowMediaConfig GetMediaConfig(nsPIDOMWindowOuter* aWindow,
+                                   uint32_t aAudioChannel) const;
 
   /* Methods for the BrowserElementAudioChannel */
   float GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
 
   void SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel,
                              float aVolume);
 
   bool GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
@@ -109,16 +132,18 @@ public:
    * AudioChannel enum.
    */
   virtual void SetDefaultVolumeControlChannel(int32_t aChannel,
                                               bool aVisible);
 
   bool AnyAudioChannelIsActive();
 
   void RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow);
+  void RefreshAgentsSuspended(nsPIDOMWindowOuter* aWindow,
+                              nsSuspendedTypes aSuspended);
 
   void RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,
                                        nsPIDOMWindowOuter* aWindow);
 
   // This method needs to know the inner window that wants to capture audio. We
   // group agents per top outer window, but we can have multiple innerWindow per
   // top outerWindow (subiframes, etc.) and we have to identify all the agents
   // just for a particular innerWindow.
@@ -150,42 +175,43 @@ public:
 
   void ChildStatusReceived(uint64_t aChildID, bool aTelephonyChannel,
                            bool aContentOrNormalChannel, bool aAnyChannel);
 
 private:
   AudioChannelService();
   ~AudioChannelService();
 
+  void RefreshAgents(nsPIDOMWindowOuter* aWindow,
+                     mozilla::function<void(AudioChannelAgent*)> aFunc);
+
   static void CreateServiceIfNeeded();
 
   /**
    * Shutdown the singleton.
    */
   static void Shutdown();
 
   void MaybeSendStatusUpdate();
 
   bool ContentOrNormalChannelIsActive();
 
   /* Send the default-volume-channel-changed notification */
   void SetDefaultVolumeControlChannelInternal(int32_t aChannel,
                                               bool aVisible, uint64_t aChildID);
 
-  struct AudioChannelConfig final
+  class AudioChannelConfig final : public WindowMediaConfig
   {
+  public:
     AudioChannelConfig()
-      : mVolume(1.0)
-      , mMuted(IsAudioChannelMutedByDefault())
+      : WindowMediaConfig(1.0, IsAudioChannelMutedByDefault(),
+                          nsISuspendedTypes::NONE_SUSPENDED)
       , mNumberOfAgents(0)
     {}
 
-    float mVolume;
-    bool mMuted;
-
     uint32_t mNumberOfAgents;
   };
 
   struct AudioChannelWindow final
   {
     explicit AudioChannelWindow(uint64_t aWindowID)
       : mWindowID(aWindowID),
         mIsAudioCaptured(false)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3764,17 +3764,17 @@ nsPIDOMWindowOuter::RefreshMediaElements
   }
 }
 
 void
 nsPIDOMWindowOuter::RefreshMediaElementsSuspended(nsSuspendedTypes aSuspended)
 {
   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   if (service) {
-    // TODO : Impelement in next patch.
+    service->RefreshAgentsSuspended(GetOuterWindow(), aSuspended);
   }
 }
 
 bool
 nsPIDOMWindowOuter::IsDisposableSuspended(nsSuspendedTypes aSuspended) const
 {
   return (aSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
           aSuspended == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -458,25 +458,26 @@ FMRadio::EnableAudioChannelAgent()
   WindowVolumeChanged(volume, muted);
 
   mAudioChannelAgentEnabled = true;
 }
 
 NS_IMETHODIMP
 FMRadio::WindowVolumeChanged(float aVolume, bool aMuted)
 {
+  // TODO : Not support to change volume now, so we just close it.
   IFMRadioService::Singleton()->EnableAudio(!aMuted);
-  // TODO: what about the volume?
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FMRadio::WindowSuspendedChanged(SuspendedTypes aSuspend)
 {
-  // TODO : implementation.
+  bool enable = (aSuspend == nsISuspendedTypes::NONE_SUSPENDED);
+  IFMRadioService::Singleton()->EnableAudio(enable);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FMRadio::WindowAudioCaptureChanged(bool aCapture)
 {
   return NS_OK;
 }
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1151,21 +1151,16 @@ void HTMLMediaElement::ResumeLoad(Preloa
   }
 }
 
 static bool IsAutoplayEnabled()
 {
   return Preferences::GetBool("media.autoplay.enabled");
 }
 
-static bool UseAudioChannelAPI()
-{
-  return Preferences::GetBool("media.useAudioChannelAPI");
-}
-
 void HTMLMediaElement::UpdatePreloadAction()
 {
   PreloadAction nextAction = PRELOAD_UNDEFINED;
   // If autoplay is set, or we're playing, we should always preload data,
   // as we'll need it to play.
   if ((IsAutoplayEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
       !mPaused)
   {
@@ -2233,16 +2228,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
     mCurrentPlayRangeStart(-1.0),
     mBegun(false),
     mLoadedDataFired(false),
     mAutoplaying(true),
     mAutoplayEnabled(true),
     mPaused(true),
     mMuted(0),
+    mAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED),
     mStatsShowing(false),
     mAllowCasting(false),
     mIsCasting(false),
     mAudioCaptured(false),
     mAudioCapturedByWindow(false),
     mPlayingBeforeSeek(false),
     mPlayingThroughTheAudioChannelBeforeSeek(false),
     mPausedForInactiveDocumentOrChannel(false),
@@ -2384,30 +2380,17 @@ HTMLMediaElement::Play(ErrorResult& aRv)
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
 nsresult
 HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
 {
-  // Prevent media element from being auto-started by a script when
-  // media.autoplay.enabled=false
-  if (!mHasUserInteraction
-      && !IsAutoplayEnabled()
-      && !EventStateManager::IsHandlingUserInput()
-      && !aCallerIsChrome) {
-    LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
-#if defined(MOZ_WIDGET_ANDROID)
-    nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
-                                         static_cast<nsIContent*>(this),
-                                         NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
-                                         false,
-                                         false);
-#endif
+  if (!IsAllowedToPlay()) {
     return NS_OK;
   }
 
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
@@ -2462,16 +2445,18 @@ HTMLMediaElement::PlayInternal(bool aCal
     case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
       DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
       break;
     }
   }
 
   mPaused = false;
   mAutoplaying = false;
+  SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   // and our preload status.
   AddRemoveSelfReference();
   UpdatePreloadAction();
   UpdateSrcMediaStreamPlaying();
   UpdateAudioChannelPlayingState();
 
   return NS_OK;
@@ -4529,22 +4514,17 @@ bool HTMLMediaElement::IsBeingDestroyed(
     docShell->IsBeingDestroyed(&isBeingDestroyed);
   }
   return isBeingDestroyed;
 }
 
 void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
 {
   bool pauseElement = NotifyOwnerDocumentActivityChangedInternal();
-  if (pauseElement && mAudioChannelAgent &&
-      // On B2G, NotifyOwnerDocumentActivityChangedInternal may return true for
-      // two reasons: the document no longer being active, or the element being
-      // paused by the audio channel.  However we are only interested in the
-      // first case here, so we need to filter out the second case.
-      (!UseAudioChannelAPI() || !ComputedMuted())) {
+  if (pauseElement && mAudioChannelAgent) {
     // If the element is being paused since we are navigating away from the
     // document, notify the audio channel agent.
     // Be careful to ignore this event during a docshell frame swap.
     auto docShell = static_cast<nsDocShell*>(OwnerDoc()->GetDocShell());
     if (!docShell) {
       return;
     }
     if (!docShell->InFrameSwap()) {
@@ -4558,24 +4538,16 @@ HTMLMediaElement::NotifyOwnerDocumentAct
 {
   nsIDocument* ownerDoc = OwnerDoc();
   if (mDecoder && !IsBeingDestroyed()) {
     mDecoder->SetElementVisibility(!ownerDoc->Hidden());
     mDecoder->NotifyOwnerActivityChanged();
   }
 
   bool pauseElement = !IsActive();
-  // Only pause the element when we start playing. If we pause without playing
-  // audio, the resource loading would be affected unexpectedly. For example,
-  // the media element is muted by default, but we don't want this behavior
-  // interrupting the loading process.
-  if (UseAudioChannelAPI() && mAudioChannelAgent) {
-    pauseElement |= ComputedMuted();
-  }
-
   SuspendOrResumeElement(pauseElement, !IsActive());
 
   if (!mPausedForInactiveDocumentOrChannel &&
       mPlayBlockedBecauseHidden &&
       !OwnerDoc()->Hidden()) {
     LOG(LogLevel::Debug, ("%p Resuming playback now that owner doc is visble.", this));
     mPlayBlockedBecauseHidden = false;
     Play();
@@ -4979,43 +4951,16 @@ NS_IMETHODIMP HTMLMediaElement::SetMozPr
 }
 
 ImageContainer* HTMLMediaElement::GetImageContainer()
 {
   VideoFrameContainer* container = GetVideoFrameContainer();
   return container ? container->GetImageContainer() : nullptr;
 }
 
-nsresult HTMLMediaElement::UpdateChannelMuteState(float aVolume, bool aMuted)
-{
-  if (mAudioChannelVolume != aVolume) {
-    mAudioChannelVolume = aVolume;
-    SetVolumeInternal();
-  }
-
-  // We have to mute this channel.
-  if (aMuted && !ComputedMuted()) {
-    SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
-    if (UseAudioChannelAPI()) {
-      DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
-    }
-  } else if (!aMuted && ComputedMuted()) {
-    SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_CHANNEL);
-    if (UseAudioChannelAPI()) {
-      DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
-    }
-  }
-
-  if (UseAudioChannelAPI()) {
-    SuspendOrResumeElement(ComputedMuted(), false);
-  }
-
-  return NS_OK;
-}
-
 bool
 HTMLMediaElement::MaybeCreateAudioChannelAgent()
 {
   if (!mAudioChannelAgent) {
     nsresult rv;
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return false;
@@ -5026,16 +4971,21 @@ HTMLMediaElement::MaybeCreateAudioChanne
                                              this);
   }
   return true;
 }
 
 bool
 HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
 {
+  // It might be resumed from remote, we should keep the audio channel agent.
+  if (IsSuspendedByAudioChannel()) {
+    return true;
+  }
+
   // Are we paused or muted
   if (mPaused || Muted()) {
     return false;
   }
 
   // If we have an error, we are not playing.
   if (mError) {
     return false;
@@ -5112,36 +5062,182 @@ HTMLMediaElement::NotifyAudioChannelAgen
     mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
     WindowVolumeChanged(volume, muted);
   } else {
     mAudioChannelAgent->NotifyStoppedPlaying();
     mAudioChannelAgent = nullptr;
   }
 }
 
-NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged(float aVolume, bool aMuted)
+NS_IMETHODIMP
+HTMLMediaElement::WindowVolumeChanged(float aVolume, bool aMuted)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  UpdateChannelMuteState(aVolume, aMuted);
-
-  if (UseAudioChannelAPI()) {
-    mPaused.SetCanPlay(!aMuted);
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("HTMLMediaElement, WindowVolumeChanged, this = %p, "
+          "aVolume = %f, aMuted = %d\n", this, aVolume, aMuted));
+
+  if (mAudioChannelVolume != aVolume) {
+    mAudioChannelVolume = aVolume;
+    SetVolumeInternal();
+  }
+
+  if (aMuted && !ComputedMuted()) {
+    SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
+  } else if (!aMuted && ComputedMuted()) {
+    SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_CHANNEL);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLMediaElement::WindowSuspendedChanged(nsSuspendedTypes aSuspend)
 {
-  // TODO : implementation.
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("HTMLMediaElement, WindowSuspendedChanged, this = %p, "
+          "aSuspend = %d\n", this, aSuspend));
+
+  switch (aSuspend) {
+    case nsISuspendedTypes::NONE_SUSPENDED:
+      ResumeFromAudioChannel();
+      break;
+    case nsISuspendedTypes::SUSPENDED_PAUSE:
+    case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
+      PauseByAudioChannel(aSuspend);
+      break;
+    case nsISuspendedTypes::SUSPENDED_BLOCK:
+      BlockByAudioChannel();
+      break;
+    case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
+      Pause();
+      break;
+    default:
+      MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+             ("HTMLMediaElement, WindowSuspendedChanged, this = %p, "
+              "Error : unknown suspended type!\n", this));
+  }
+
   return NS_OK;
 }
 
+void
+HTMLMediaElement::ResumeFromAudioChannel()
+{
+  if (!IsSuspendedByAudioChannel()) {
+    return;
+  }
+
+  switch (mAudioChannelSuspended) {
+    case nsISuspendedTypes::SUSPENDED_PAUSE:
+    case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
+      ResumeFromAudioChannelPaused(mAudioChannelSuspended);
+      break;
+    case nsISuspendedTypes::SUSPENDED_BLOCK:
+      ResumeFromAudioChannelBlocked();
+      break;
+    default:
+      MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+             ("HTMLMediaElement, ResumeFromAudioChannel, this = %p, "
+              "Error : resume without suspended!\n", this));
+  }
+}
+
+void
+HTMLMediaElement::ResumeFromAudioChannelPaused(nsSuspendedTypes aSuspend)
+{
+  SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+  nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+  DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
+}
+
+void
+HTMLMediaElement::ResumeFromAudioChannelBlocked()
+{
+  SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+  mPaused.SetCanPlay(true);
+  SuspendOrResumeElement(false /* resume */, false);
+}
+
+void
+HTMLMediaElement::PauseByAudioChannel(nsSuspendedTypes aSuspend)
+{
+  if (IsSuspendedByAudioChannel()) {
+    return;
+  }
+
+  SetAudioChannelSuspended(aSuspend);
+  Pause();
+  DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
+}
+
+void
+HTMLMediaElement::BlockByAudioChannel()
+{
+  if (IsSuspendedByAudioChannel()) {
+    return;
+  }
+
+  SetAudioChannelSuspended(nsISuspendedTypes::SUSPENDED_BLOCK);
+  SuspendOrResumeElement(true /* suspend */, false);
+  mPaused.SetCanPlay(false);
+}
+
+void
+HTMLMediaElement::SetAudioChannelSuspended(nsSuspendedTypes aSuspend)
+{
+  if (mAudioChannelSuspended == aSuspend) {
+    return;
+  }
+
+  mAudioChannelSuspended = aSuspend;
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("HTMLMediaElement, SetAudioChannelSuspended, this = %p, "
+          "aSuspend = %d\n", this, aSuspend));
+}
+
+bool
+HTMLMediaElement::IsSuspendedByAudioChannel() const
+{
+  return (mAudioChannelSuspended & nsISuspendedTypes::SUSPENDED_PAUSE ||
+          mAudioChannelSuspended & nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
+          mAudioChannelSuspended & nsISuspendedTypes::SUSPENDED_BLOCK);
+}
+
+bool
+HTMLMediaElement::IsAllowedToPlay()
+{
+  // Prevent media element from being auto-started by a script when
+  // media.autoplay.enabled=false
+  if (!mHasUserInteraction &&
+      !IsAutoplayEnabled() &&
+      !EventStateManager::IsHandlingUserInput() &&
+      !nsContentUtils::IsCallerChrome()) {
+#if defined(MOZ_WIDGET_ANDROID)
+    nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+                                         static_cast<nsIContent*>(this),
+                                         NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
+                                         false,
+                                         false);
+#endif
+    return false;
+  }
+
+  // The MediaElement can start playback until it's resumed by audio channel.
+  if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
+      mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
+    return false;
+  }
+
+  return true;
+}
+
 #ifdef MOZ_EME
 MediaKeys*
 HTMLMediaElement::GetMediaKeys() const
 {
   return mMediaKeys;
 }
 
 bool
@@ -5470,16 +5566,22 @@ HTMLMediaElement::ComputedVolume() const
 }
 
 bool
 HTMLMediaElement::ComputedMuted() const
 {
   return (mMuted & MUTED_BY_AUDIO_CHANNEL);
 }
 
+nsSuspendedTypes
+HTMLMediaElement::ComputedSuspended() const
+{
+  return mAudioChannelSuspended;
+}
+
 bool
 HTMLMediaElement::IsCurrentlyPlaying() const
 {
   // We have playable data, but we still need to check whether data is "real"
   // current data.
   if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
       !IsPlaybackEnded()) {
 
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -32,16 +32,17 @@
 
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
+typedef uint32_t nsSuspendedTypes;
 
 namespace mozilla {
 class DecoderDoctorDiagnostics;
 class DOMMediaStream;
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
@@ -711,19 +712,20 @@ public:
   // Returns true if the media element is being destroyed. Used in
   // dormancy checks to prevent dormant processing for an element
   // that will soon be gone.
   bool IsBeingDestroyed();
 
   IMPL_EVENT_HANDLER(mozinterruptbegin)
   IMPL_EVENT_HANDLER(mozinterruptend)
 
-  // This is for testing only
+  // These are used for testing only
   float ComputedVolume() const;
   bool ComputedMuted() const;
+  nsSuspendedTypes ComputedSuspended() const;
 
 protected:
   virtual ~HTMLMediaElement();
 
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
   class MediaStreamTrackListener;
   class StreamListener;
@@ -1107,19 +1109,16 @@ protected:
 #ifdef MOZ_EME
   void ReportEMETelemetry();
 #endif
   void ReportMSETelemetry();
 
   // Check the permissions for audiochannel.
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
-  // This method does the check for muting/nmuting the audio channel.
-  nsresult UpdateChannelMuteState(float aVolume, bool aMuted);
-
   // Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the
   // seek target, or PrevSyncPoint if a quicker but less precise seek is
   // desired, and we'll seek to the sync point (keyframe and/or start of the
   // next block of audio samples) preceeding seek target.
   void Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
 
   // A method to check if we are playing through the AudioChannel.
   bool IsPlayingThroughTheAudioChannel() const;
@@ -1144,16 +1143,48 @@ protected:
 
   // Notifies the audio channel agent when the element starts or stops playing.
   void NotifyAudioChannelAgent(bool aPlaying);
 
   // Creates the audio channel agent if needed.  Returns true if the audio
   // channel agent is ready to be used.
   bool MaybeCreateAudioChannelAgent();
 
+  /**
+   * We have different kinds of suspended cases,
+   * - SUSPENDED_PAUSE
+   * It's used when we temporary lost platform audio focus. MediaElement can
+   * only be resumed when we gain the audio focus again.
+   *
+   * - SUSPENDED_PAUSE_DISPOSABLE
+   * It's used when user press the pause botton on the remote media-control.
+   * MediaElement can be resumed by reomte media-control or via play().
+   *
+   * - SUSPENDED_BLOCK
+   * It's used to reduce the power comsuption, we won't play the auto-play
+   * audio/video in the page we have never visited before. MediaElement would
+   * be resumed when the page is active. See bug647429 for more details.
+   *
+   * - SUSPENDED_STOP_DISPOSABLE
+   * When we permanently lost platform audio focus, we shuold stop playing
+   * and stop the audio channel agent. MediaElement can only be restarted by
+   * play().
+   */
+  void PauseByAudioChannel(nsSuspendedTypes aSuspend);
+  void BlockByAudioChannel();
+
+  void ResumeFromAudioChannel();
+  void ResumeFromAudioChannelPaused(nsSuspendedTypes aSuspend);
+  void ResumeFromAudioChannelBlocked();
+
+  bool IsSuspendedByAudioChannel() const;
+  void SetAudioChannelSuspended(nsSuspendedTypes aSuspend);
+
+  bool IsAllowedToPlay();
+
   class nsAsyncEventRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
@@ -1363,16 +1394,17 @@ protected:
   enum MutedReasons {
     MUTED_BY_CONTENT               = 0x01,
     MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
     MUTED_BY_AUDIO_CHANNEL         = 0x04,
     MUTED_BY_AUDIO_TRACK           = 0x08
   };
 
   uint32_t mMuted;
+  nsSuspendedTypes mAudioChannelSuspended;
 
   // True if the media statistics are currently being shown by the builtin
   // video controls
   bool mStatsShowing;
 
   // The following two fields are here for the private storage of the builtin
   // video controls, and control 'casting' of the video to external devices
   // (TVs, projectors etc.)
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -300,21 +300,16 @@ public:
   }
 
 private:
   float mVolume;
   bool mLastInputMuted;
   bool mSuspended;
 };
 
-static bool UseAudioChannelAPI()
-{
-  return Preferences::GetBool("media.useAudioChannelAPI");
-}
-
 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode,
                                    mAudioChannelAgent,
                                    mOfflineRenderingPromise)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
@@ -326,17 +321,17 @@ AudioDestinationNode::AudioDestinationNo
                                            AudioChannel aChannel,
                                            uint32_t aNumberOfChannels,
                                            uint32_t aLength, float aSampleRate)
   : AudioNode(aContext, aIsOffline ? aNumberOfChannels : 2,
               ChannelCountMode::Explicit, ChannelInterpretation::Speakers)
   , mFramesToProduce(aLength)
   , mAudioChannel(AudioChannel::Normal)
   , mIsOffline(aIsOffline)
-  , mAudioChannelAgentPlaying(false)
+  , mAudioChannelSuspended(false)
   , mExtraCurrentTimeSinceLastStartedBlocking(0)
   , mExtraCurrentTimeUpdatedSinceLastStableState(false)
   , mCaptured(false)
 {
   MediaStreamGraph* graph = aIsOffline ?
                             MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
                             MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, aChannel);
   AudioNodeEngine* engine = aIsOffline ?
@@ -498,48 +493,46 @@ AudioDestinationNode::WrapObject(JSConte
 void
 AudioDestinationNode::StartRendering(Promise* aPromise)
 {
   mOfflineRenderingPromise = aPromise;
   mOfflineRenderingRef.Take(this);
   mStream->Graph()->StartNonRealtimeProcessing(mFramesToProduce);
 }
 
-void
-AudioDestinationNode::SetCanPlay(float aVolume, bool aMuted)
-{
-  if (!mStream) {
-    return;
-  }
-
-  mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, !aMuted);
-  mStream->SetAudioOutputVolume(&gWebAudioOutputKey, aVolume);
-}
-
 NS_IMETHODIMP
 AudioDestinationNode::WindowVolumeChanged(float aVolume, bool aMuted)
 {
-  if (aMuted != mAudioChannelAgentPlaying) {
-    mAudioChannelAgentPlaying = aMuted;
-
-    if (UseAudioChannelAPI()) {
-      Context()->DispatchTrustedEvent(
-        !aMuted ? NS_LITERAL_STRING("mozinterruptend")
-                : NS_LITERAL_STRING("mozinterruptbegin"));
-    }
+  if (!mStream) {
+    return NS_OK;
   }
 
-  SetCanPlay(aVolume, aMuted);
+  float volume = aMuted ? 0.0 : aVolume;
+  mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AudioDestinationNode::WindowSuspendedChanged(nsSuspendedTypes aSuspend)
 {
-  // TODO : implementation.
+  if (!mStream) {
+    return NS_OK;
+  }
+
+  bool suspended = (aSuspend != nsISuspendedTypes::NONE_SUSPENDED);
+  if (mAudioChannelSuspended == suspended) {
+    return NS_OK;
+  }
+
+  mAudioChannelSuspended = suspended;
+  Context()->DispatchTrustedEvent(!suspended ?
+    NS_LITERAL_STRING("mozinterruptend") :
+    NS_LITERAL_STRING("mozinterruptbegin"));
+
+  mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, !suspended);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AudioDestinationNode::WindowAudioCaptureChanged(bool aCapture)
 {
   MOZ_ASSERT(mAudioChannelAgent);
 
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -88,33 +88,31 @@ public:
 
 protected:
   virtual ~AudioDestinationNode();
 
 private:
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
   bool CheckAudioChannelPermissions(AudioChannel aValue);
 
-  void SetCanPlay(float aVolume, bool aMuted);
-
   void NotifyStableState();
   void ScheduleStableStateNotification();
 
   SelfReference<AudioDestinationNode> mOfflineRenderingRef;
   uint32_t mFramesToProduce;
 
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
   RefPtr<MediaInputPort> mCaptureStreamPort;
 
   RefPtr<Promise> mOfflineRenderingPromise;
 
   // Audio Channel Type.
   AudioChannel mAudioChannel;
   bool mIsOffline;
-  bool mAudioChannelAgentPlaying;
+  bool mAudioChannelSuspended;
 
   TimeStamp mStartedBlockingDueToBeingOnlyNode;
   StreamTime mExtraCurrentTimeSinceLastStartedBlocking;
   bool mExtraCurrentTimeUpdatedSinceLastStableState;
   bool mCaptured;
 };
 
 } // namespace dom
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -731,17 +731,21 @@ nsSpeechTask::WindowVolumeChanged(float 
 {
   SetAudioOutputVolume(aMuted ? 0.0 : mVolume * aVolume);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::WindowSuspendedChanged(nsSuspendedTypes aSuspend)
 {
-  // TODO : implementation.
+  if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED) {
+    Resume();
+  } else {
+    Pause();
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::WindowAudioCaptureChanged(bool aCapture)
 {
   // This is not supported yet.
   return NS_OK;
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
@@ -658,22 +658,21 @@ nsSynthVoiceRegistry::SpeakUtterance(Spe
     aUtterance.mVoice->GetVoiceURI(uri);
   }
 
   // Get current audio volume to apply speech call
   float volume = aUtterance.Volume();
   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   if (service) {
     if (nsCOMPtr<nsPIDOMWindowInner> topWindow = aUtterance.GetOwner()) {
-      float audioVolume = 1.0f;
-      bool muted = false;
-      service->GetState(topWindow->GetOuterWindow(),
-                        static_cast<uint32_t>(AudioChannelService::GetDefaultAudioChannel()),
-                        &audioVolume, &muted);
-      volume = muted ? 0.0f : audioVolume * volume; 
+      // TODO : use audio channel agent, open new bug to fix it.
+      uint32_t channel = static_cast<uint32_t>(AudioChannelService::GetDefaultAudioChannel());
+      WindowMediaConfig config = service->GetMediaConfig(topWindow->GetOuterWindow(),
+                                                         channel);
+      volume = config.mMuted ? 0.0f : config.mVolume * volume;
     }
   }
 
   RefPtr<nsSpeechTask> task;
   if (XRE_IsContentProcess()) {
     task = new SpeechTaskChild(&aUtterance);
     SpeechSynthesisRequestChild* actor =
       new SpeechSynthesisRequestChild(static_cast<SpeechTaskChild*>(task.get()));
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -1829,17 +1829,19 @@ nsNPAPIPluginInstance::WindowVolumeChang
   nsresult rv = SetMuted(aMuted);
   NS_WARN_IF(NS_FAILED(rv));
   return rv;
 }
 
 NS_IMETHODIMP
 nsNPAPIPluginInstance::WindowSuspendedChanged(nsSuspendedTypes aSuspend)
 {
-  // TODO : implementation.
+  // It doesn't support suspended, so we just do something like mute/unmute.
+  WindowVolumeChanged(1.0, /* useless */
+                      aSuspend != nsISuspendedTypes::NONE_SUSPENDED);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNPAPIPluginInstance::WindowAudioCaptureChanged(bool aCapture)
 {
   return NS_OK;
 }
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -169,9 +169,11 @@ partial interface HTMLMediaElement {
 #endif
 
 // This is just for testing
 partial interface HTMLMediaElement {
   [Pref="media.useAudioChannelService.testing"]
   readonly attribute double computedVolume;
   [Pref="media.useAudioChannelService.testing"]
   readonly attribute boolean computedMuted;
+  [Pref="media.useAudioChannelService.testing"]
+  readonly attribute unsigned long computedSuspended;
 };