Backed out 7 changesets (bug 1309162) for valgrind failures a=backout
authorWes Kocher <wkocher@mozilla.com>
Mon, 28 Nov 2016 13:16:31 -0800
changeset 324548 eb95159069bb4e50d52a004d6df9fcdf1f9eae42
parent 324547 36be06539887d1dfcdb689e5c6f88973181059a3
child 324549 fc1af61ec6deccae5f6b4b2b36592b84161f0040
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersbackout
bugs1309162
milestone53.0a1
backs out3f0ad2d2c5941e532a934eeb9a04c248ab01e450
17c744162fbe53642a4d88848098d12c80f97bd4
2015de6577af9b626dad5576e92120bccb59e719
fe1771c913099ef108b6f856cb7fa6e19bfc0c5f
b5ab5eae225d0dd31c4c2ce7de86a50094535bdd
16ceabbf8eef0c74d91118b6818b1f884099023b
6b09c6adcfa5066a4a76241d3b8c037274d0a4b7
Backed out 7 changesets (bug 1309162) for valgrind failures a=backout Backed out changeset 3f0ad2d2c594 (bug 1309162) Backed out changeset 17c744162fbe (bug 1309162) Backed out changeset 2015de6577af (bug 1309162) Backed out changeset fe1771c91309 (bug 1309162) Backed out changeset b5ab5eae225d (bug 1309162) Backed out changeset 16ceabbf8eef (bug 1309162) Backed out changeset 6b09c6adcfa5 (bug 1309162)
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -118,16 +118,34 @@ static mozilla::LazyLogModule gMediaElem
 #include "mozilla/EventStateManager.h"
 
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/VideoPlaybackQuality.h"
 
 using namespace mozilla::layers;
 using mozilla::net::nsMediaFragmentURIParser;
 
+class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent
+{
+  RefPtr<mozilla::dom::HTMLMediaElement> mElement;
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+public:
+  explicit AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement
+                                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+    : mElement(aElement)
+  {
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  }
+
+  ~AutoNotifyAudioChannelAgent()
+  {
+    mElement->UpdateAudioChannelPlayingState();
+  }
+};
+
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between progress events as defined by spec
 static const uint32_t PROGRESS_MS = 350;
 
 // Number of milliseconds of no data before a stall event is fired as defined by spec
 static const uint32_t STALL_MS = 3000;
@@ -520,476 +538,16 @@ void HTMLMediaElement::ReportLoadError(c
                                   NS_LITERAL_CSTRING("Media"),
                                   OwnerDoc(),
                                   nsContentUtils::eDOM_PROPERTIES,
                                   aMsg,
                                   aParams,
                                   aParamCount);
 }
 
-static bool IsAutoplayEnabled()
-{
-  return Preferences::GetBool("media.autoplay.enabled");
-}
-
-class HTMLMediaElement::AudioChannelAgentCallback final :
-  public nsIAudioChannelAgentCallback
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)
-
-  AudioChannelAgentCallback(HTMLMediaElement* aOwner,
-                            AudioChannel aChannel)
-    : mOwner(aOwner)
-    , mAudioChannel(aChannel)
-    , mAudioChannelVolume(1.0)
-    , mPlayingThroughTheAudioChannel(false)
-    , mAudioCapturedByWindow(false)
-    , mIsOwnerAudible(IsOwnerAudible())
-    , mSuspended(nsISuspendedTypes::NONE_SUSPENDED)
-  {
-    MOZ_ASSERT(mOwner);
-    MaybeCreateAudioChannelAgent();
-  }
-
-  void
-  UpdateAudioChannelPlayingState(bool aForcePlaying = false)
-  {
-    bool playingThroughTheAudioChannel =
-      aForcePlaying || IsPlayingThroughTheAudioChannel();
-
-    if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
-      if (!MaybeCreateAudioChannelAgent()) {
-        return;
-      }
-
-      mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
-      NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
-    }
-  }
-
-  void
-  NotifyPlayStarted()
-  {
-    // Reset the suspend type because the media element might be paused by
-    // audio channel before calling play(). eg. paused by Fennec media control,
-    // but resumed it from page.
-    SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
-    UpdateAudioChannelPlayingState();
-  }
-
-  NS_IMETHODIMP
-  WindowVolumeChanged(float aVolume, bool aMuted) override
-  {
-    MOZ_ASSERT(mAudioChannelAgent);
-
-    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-           ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
-            "this = %p, aVolume = %f, aMuted = %d\n", this, aVolume, aMuted));
-
-    if (mAudioChannelVolume != aVolume) {
-      mAudioChannelVolume = aVolume;
-      mOwner->SetVolumeInternal();
-    }
-
-    const uint32_t muted = mOwner->mMuted;
-    if (aMuted && !mOwner->ComputedMuted()) {
-      mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL);
-    } else if (!aMuted && mOwner->ComputedMuted()) {
-      mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL);
-    }
-
-    return NS_OK;
-  }
-
-  NS_IMETHODIMP
-  WindowSuspendChanged(SuspendTypes aSuspend) override
-  {
-    MOZ_ASSERT(mAudioChannelAgent);
-
-    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-           ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
-            "this = %p, aSuspend = %d\n", this, aSuspend));
-
-    switch (aSuspend) {
-      case nsISuspendedTypes::NONE_SUSPENDED:
-        Resume();
-        break;
-      case nsISuspendedTypes::SUSPENDED_PAUSE:
-      case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
-      case nsISuspendedTypes::SUSPENDED_BLOCK:
-        Suspend(aSuspend);
-        break;
-      case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
-        Stop();
-        break;
-      default:
-        MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-               ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
-                "this = %p, Error : unknown suspended type!\n", this));
-    }
-    return NS_OK;
-  }
-
-  NS_IMETHODIMP
-  WindowAudioCaptureChanged(bool aCapture) override
-  {
-    MOZ_ASSERT(mAudioChannelAgent);
-
-    if (mAudioCapturedByWindow != aCapture) {
-      mAudioCapturedByWindow = aCapture;
-      AudioCaptureStreamChangeIfNeeded();
-    }
-    return NS_OK;
-  }
-
-  void
-  AudioCaptureStreamChangeIfNeeded()
-  {
-    if (!IsPlayingStarted()) {
-      return;
-    }
-
-    if (!mOwner->HasAudio()) {
-      return;
-    }
-
-    mOwner->AudioCaptureStreamChange(mAudioCapturedByWindow);
-  }
-
-  void
-  NotifyAudioPlaybackChanged(AudibleChangedReasons aReason)
-  {
-    if (!IsPlayingStarted()) {
-      return;
-    }
-
-    bool newAudibleState = IsOwnerAudible();
-    if (mIsOwnerAudible == newAudibleState) {
-      return;
-    }
-
-    mIsOwnerAudible = newAudibleState;
-    mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
-  }
-
-  bool
-  IsAllowedToPlay()
-  {
-    // The media element has already been paused or blocked, so it can't start
-    // playback again by script or user's intend until resuming by audio channel.
-    if (mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
-        mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
-      return false;
-    }
-
-    // If the tab hasn't been activated yet, the media element in that tab can't
-    // be playback now until the tab goes to foreground first time or user clicks
-    // the unblocking tab icon.
-    if (!IsTabActivated()) {
-      // Even we haven't start playing yet, we still need to notify the audio
-      // channe system because we need to receive the resume notification later.
-      UpdateAudioChannelPlayingState(true /* force to start */);
-      return false;
-    }
-
-    return true;
-  }
-
-  float
-  GetEffectiveVolume() const
-  {
-    return mOwner->Volume() * mAudioChannelVolume;
-  }
-
-  SuspendTypes
-  GetSuspendType() const
-  {
-    return mSuspended;
-  }
-
-private:
-  ~AudioChannelAgentCallback() {};
-
-  bool
-  MaybeCreateAudioChannelAgent()
-  {
-    if (mAudioChannelAgent) {
-      return true;
-    }
-
-    mAudioChannelAgent = new AudioChannelAgent();
-    nsresult rv = mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(),
-                                           static_cast<int32_t>(mAudioChannel),
-                                           this);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      mAudioChannelAgent = nullptr;
-      MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-             ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
-              "the audio channel agent, this = %p\n", this));
-      return false;
-    }
-
-    return true;
-  }
-
-  void
-  NotifyAudioChannelAgent(bool aPlaying)
-  {
-    MOZ_ASSERT(mAudioChannelAgent);
-
-    // This is needed to pass nsContentUtils::IsCallerChrome().
-    // AudioChannel API should not called from content but it can happen that
-    // this method has some content JS in its stack.
-    AutoNoJSAPI nojsapi;
-
-    if (aPlaying) {
-      AudioPlaybackConfig config;
-      nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&config,
-                                                             IsOwnerAudible());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return;
-      }
-
-      WindowVolumeChanged(config.mVolume, config.mMuted);
-      WindowSuspendChanged(config.mSuspend);
-    } else {
-      mAudioChannelAgent->NotifyStoppedPlaying();
-    }
-  }
-
-  void
-  SetSuspended(SuspendTypes aSuspend)
-  {
-    if (mSuspended == aSuspend) {
-      return;
-    }
-
-    MaybeNotifyMediaResumed(aSuspend);
-    mSuspended = aSuspend;
-    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-           ("HTMLMediaElement::AudioChannelAgentCallback, SetAudioChannelSuspended, "
-            "this = %p, aSuspend = %d\n", this, aSuspend));
-
-    NotifyAudioPlaybackChanged(
-      AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
-  }
-
-  void
-  Resume()
-  {
-    if (!IsSuspended() && mOwner->Paused()) {
-      MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
-             ("HTMLMediaElement::AudioChannelAgentCallback, ResumeFromAudioChannel, "
-              "this = %p, Error : resume without suspended!\n", this));
-      return;
-    }
-
-    SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
-    nsresult rv = mOwner->Play();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-  }
-
-  void
-  Suspend(SuspendTypes aSuspend)
-  {
-    if (IsSuspended()) {
-      return;
-    }
-
-    SetSuspended(aSuspend);
-    if (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE ||
-        aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE) {
-        nsresult rv = mOwner->Pause();
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return;
-        }
-    }
-  }
-
-  void
-  Stop()
-  {
-    SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
-    mOwner->Pause();
-  }
-
-  bool
-  IsPlayingStarted()
-  {
-    if (MaybeCreateAudioChannelAgent()) {
-      return mAudioChannelAgent->IsPlayingStarted();
-    }
-    return false;
-  }
-
-  void
-  MaybeNotifyMediaResumed(SuspendTypes aSuspend)
-  {
-    // In fennec, we should send the notification when media is resumed from the
-    // pause-disposable which was called by media control.
-    if (mSuspended != nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE &&
-        aSuspend != nsISuspendedTypes::NONE_SUSPENDED) {
-      return;
-    }
-
-    if (!IsPlayingStarted()) {
-      return;
-    }
-
-    uint64_t windowID = mAudioChannelAgent->WindowID();
-    NS_DispatchToMainThread(NS_NewRunnableFunction([windowID]() -> void {
-      nsCOMPtr<nsIObserverService> observerService =
-        services::GetObserverService();
-      if (NS_WARN_IF(!observerService)) {
-        return;
-      }
-
-      nsCOMPtr<nsISupportsPRUint64> wrapper =
-        do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
-      if (NS_WARN_IF(!wrapper)) {
-         return;
-      }
-
-      wrapper->SetData(windowID);
-      observerService->NotifyObservers(wrapper,
-                                       "media-playback-resumed",
-                                       u"active");
-    }));
-  }
-
-  bool
-  IsTabActivated()
-  {
-    if (MaybeCreateAudioChannelAgent()) {
-      return !mAudioChannelAgent->ShouldBlockMedia();
-    }
-    return false;
-  }
-
-  bool
-  IsSuspended() const
-  {
-    return (mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
-            mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
-            mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
-  }
-
-  bool
-  IsOwnerAudible() const
-  {
-    // Muted or the volume should not be ~0
-    if (mOwner->Muted() || (std::fabs(mOwner->Volume()) <= 1e-7)) {
-      return false;
-    }
-
-    // No sound can be heard during suspending.
-    if (IsSuspended()) {
-      return false;
-    }
-
-    // Silent audio track.
-    if (!mOwner->mIsAudioTrackAudible) {
-      return false;
-    }
-
-    return true;
-  }
-
-  bool
-  IsPlayingThroughTheAudioChannel() const
-  {
-    // If we have an error, we are not playing.
-    if (mOwner->GetError()) {
-      return false;
-    }
-
-    // It might be resumed from remote, we should keep the audio channel agent.
-    if (IsSuspended()) {
-      return true;
-    }
-
-    // Are we paused
-    if (mOwner->mPaused) {
-      return false;
-    }
-
-    // We should consider any bfcached page or inactive document as non-playing.
-    if (!mOwner->IsActive()) {
-      return false;
-    }
-
-    // A loop always is playing
-    if (mOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
-      return true;
-    }
-
-    // If we are actually playing...
-    if (mOwner->IsCurrentlyPlaying()) {
-      return true;
-    }
-
-    // If we are playing an external stream.
-    if (mOwner->mSrcAttrStream) {
-      return true;
-    }
-
-    return false;
-  }
-
-  RefPtr<AudioChannelAgent> mAudioChannelAgent;
-  HTMLMediaElement* mOwner;
-
-  AudioChannel mAudioChannel;
-  // The audio channel volume
-  float mAudioChannelVolume;
-  // Is this media element playing?
-  bool mPlayingThroughTheAudioChannel;
-  // True if the sound is being captured by the window.
-  bool mAudioCapturedByWindow;
-  // True if media element is audible for users.
-  bool mIsOwnerAudible;
-  // 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().
-  SuspendTypes mSuspended;
-};
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLMediaElement::AudioChannelAgentCallback)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLMediaElement::AudioChannelAgentCallback)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement::AudioChannelAgentCallback)
-  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback)
-
 class HTMLMediaElement::ChannelLoader final {
 public:
   NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
 
   void LoadInternal(HTMLMediaElement* aElement)
   {
     if (mCancelled) {
       return;
@@ -1249,17 +807,17 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMedia
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream);
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
@@ -1274,31 +832,32 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
     tmp->EndSrcMediaStreamPlayback();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream)
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLMediaElement)
+  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 // nsIDOMHTMLMediaElement
 NS_IMPL_URI_ATTR(HTMLMediaElement, Src, src)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, Controls, controls)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, Autoplay, autoplay)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, Loop, loop)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, DefaultMuted, muted)
@@ -2090,16 +1649,21 @@ void HTMLMediaElement::ResumeLoad(Preloa
     // We were loading from a child <source> element. Try to resume the
     // load of that child, and if that fails, try the next child.
     if (NS_FAILED(LoadResource())) {
       LoadFromSourceChildren();
     }
   }
 }
 
+static bool IsAutoplayEnabled()
+{
+  return Preferences::GetBool("media.autoplay.enabled");
+}
+
 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)
   {
@@ -2491,16 +2055,20 @@ HTMLMediaElement::Seek(double aTime,
   // TODO: The spec requires us to update the current time to reflect the
   //       actual seek target before beginning the synchronous section, but
   //       that requires changing all MediaDecoderReaders to support telling
   //       us the fastSeek target, and it's currently not possible to get
   //       this information as we don't yet control the demuxer for all
   //       MediaDecoderReaders.
 
   mPlayingBeforeSeek = IsPotentiallyPlaying();
+  // Set the Variable if the Seekstarted while active playing
+  if (mPlayingThroughTheAudioChannel) {
+    mPlayingThroughTheAudioChannelBeforeSeek = true;
+  }
 
   // The media backend is responsible for dispatching the timeupdate
   // event if it changes the playback position as a result of the seek.
   LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
   nsresult rv = mDecoder->Seek(aTime, aSeekType, promise);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
@@ -3426,21 +2994,24 @@ 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),
     mEventDeliveryPaused(false),
     mIsRunningLoadMethod(false),
     mIsDoingExplicitLoad(false),
     mIsLoadingFromSourceChildren(false),
     mDelayingLoadEvent(false),
     mIsRunningSelectResource(false),
     mHaveQueuedSelectResource(false),
@@ -3451,43 +3022,47 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mShuttingDown(false),
     mSuspendedForPreloadNone(false),
     mSrcStreamIsPlaying(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mWaitingForKey(NOT_WAITING_FOR_KEY),
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
-    mAudioChannel(AudioChannelService::GetDefaultAudioChannel()),
+    mAudioChannelVolume(1.0),
+    mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
     mHasUserInteraction(false),
     mFirstFrameLoaded(false),
     mDefaultPlaybackStartPosition(0.0),
     mIsAudioTrackAudible(false),
+    mAudible(IsAudible()),
     mVisibilityState(Visibility::APPROXIMATELY_NONVISIBLE),
-    mErrorSink(new ErrorSink(this)),
-    mAudioChannelWrapper(new AudioChannelAgentCallback(this, mAudioChannel))
+    mErrorSink(new ErrorSink(this))
 {
   ErrorResult rv;
 
   double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
   SetVolume(defaultVolume, rv);
 
+  mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
+
   mPaused.SetOuter(this);
 
   RegisterActivityObserver();
   NotifyOwnerDocumentActivityChanged();
 
   MOZ_ASSERT(NS_IsMainThread());
   mWatchManager.Watch(mDownloadSuspendedByCache, &HTMLMediaElement::UpdateReadyStateInternal);
   // Paradoxically, there is a self-edge whereby UpdateReadyStateInternal refuses
   // to run until mReadyState reaches at least HAVE_METADATA by some other means.
   mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateReadyStateInternal);
 
   mShutdownObserver->Subscribe(this);
+  MaybeCreateAudioChannelAgent();
 }
 
 HTMLMediaElement::~HTMLMediaElement()
 {
   NS_ASSERTION(!mHasSelfReference,
                "How can we be destroyed if we're still holding a self reference?");
 
   mShutdownObserver->Unsubscribe();
@@ -3518,16 +3093,17 @@ HTMLMediaElement::~HTMLMediaElement()
   NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
     "Destroyed media element should no longer be in element table");
 
   if (mChannelLoader) {
     mChannelLoader->Cancel();
   }
 
   WakeLockRelease();
+  mAudioChannelAgent = nullptr;
 }
 
 void HTMLMediaElement::StopSuspendingAfterFirstFrame()
 {
   mAllowSuspendAfterFirstFrame = false;
   if (!mSuspendedAfterFirstFrame)
     return;
   mSuspendedAfterFirstFrame = false;
@@ -3568,17 +3144,17 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     return;
   }
 
   nsresult rv = PlayInternal();
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 
-  UpdateCustomPolicyAfterPlayed();
+  OpenUnsupportedMediaWithExternalAppIfNeeded();
 }
 
 nsresult
 HTMLMediaElement::PlayInternal()
 {
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
@@ -3606,23 +3182,28 @@ HTMLMediaElement::PlayInternal()
 
   if (mCurrentPlayRangeStart == -1.0) {
     mCurrentPlayRangeStart = CurrentTime();
   }
 
   bool oldPaused = mPaused;
   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();
+
+  // We should check audio channel playing state before dispatching any events,
+  // because we don't want to dispatch events for blocked media. For blocked
+  // media, the event would be pending until media is resumed.
   // TODO: If the playback has ended, then the user agent must set
   // seek to the effective start.
   if (oldPaused) {
     DispatchAsyncEvent(NS_LITERAL_STRING("play"));
     switch (mReadyState) {
     case nsIDOMHTMLMediaElement::HAVE_NOTHING:
       DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
       break;
@@ -3657,17 +3238,17 @@ NS_IMETHODIMP HTMLMediaElement::Play()
     return NS_OK;
   }
 
   nsresult rv = PlayInternal();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  UpdateCustomPolicyAfterPlayed();
+  OpenUnsupportedMediaWithExternalAppIfNeeded();
   return NS_OK;
 }
 
 HTMLMediaElement::WakeLockBoolWrapper&
 HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val)
 {
   if (mValue == val) {
     return *this;
@@ -3789,17 +3370,16 @@ bool HTMLMediaElement::ParseAttribute(in
     if (aAttribute == nsGkAtoms::crossorigin) {
       ParseCORSValue(aValue, aResult);
       return true;
     }
     if (aAttribute == nsGkAtoms::preload) {
       return aResult.ParseEnumValue(aValue, kPreloadTable, false);
     }
 
-    // Remove the b2g-specific audio channel setting in bug1299390.
     if (aAttribute == nsGkAtoms::mozaudiochannel) {
       const nsAttrValue::EnumTable* table =
         AudioChannelService::GetAudioChannelTable();
       MOZ_ASSERT(table);
 
       bool parsed = aResult.ParseEnumValue(aValue, table, false, &table[0]);
       if (!parsed) {
         return false;
@@ -4404,16 +3984,17 @@ nsresult HTMLMediaElement::FinishDecoder
     mChannelLoader = nullptr;
   }
 
   AddMediaElementToURITable();
 
   // We may want to suspend the new stream now.
   // This will also do an AddRemoveSelfReference.
   NotifyOwnerDocumentActivityChanged();
+  UpdateAudioChannelPlayingState();
 
   if (!mPaused) {
     SetPlayedOrSeeked(true);
     if (!mPausedForInactiveDocumentOrChannel) {
       rv = mDecoder->Play();
     }
   }
 
@@ -4624,19 +4205,16 @@ void HTMLMediaElement::UpdateSrcMediaStr
     SetVolumeInternal();
 
     VideoFrameContainer* container = GetVideoFrameContainer();
     if (mSelectedVideoStreamTrack && container) {
       mSelectedVideoStreamTrack->AddVideoOutput(container);
     }
 
     SetCapturedOutputStreamsEnabled(true); // Unmute
-    // If the input is a media stream, we don't check its data and always regard
-    // it as audible when it's playing.
-    SetAudibleState(true);
   } else {
     if (stream) {
       mSrcStreamPausedCurrentTime = CurrentTime();
 
       stream->RemoveListener(mMediaStreamListener);
 
       stream->RemoveAudioOutput(this);
       VideoFrameContainer* container = GetVideoFrameContainer();
@@ -4650,16 +4228,20 @@ void HTMLMediaElement::UpdateSrcMediaStr
     // called and that will remove all listeners/outputs.
 
     mWatchManager.Unwatch(*mMediaStreamListener,
         &HTMLMediaElement::UpdateReadyStateInternal);
 
     mMediaStreamListener->Forget();
     mMediaStreamListener = nullptr;
   }
+
+  // If the input is a media stream, we don't check its data and always regard
+  // it as audible when it's playing.
+  SetAudibleState(shouldPlay);
 }
 
 void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
 {
   NS_ASSERTION(!mSrcStream && !mMediaStreamListener && !mMediaStreamSizeListener,
                "Should have been ended already");
 
   mSrcStream = aStream;
@@ -4836,16 +4418,21 @@ void HTMLMediaElement::ProcessMediaFragm
   }
 }
 
 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
                                       nsAutoPtr<const MetadataTags> aTags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  // If the element is gaining or losing an audio track, we need to notify
+  // the audio channel agent so that the correct audio-playback events will
+  // get dispatched.
+  AutoNotifyAudioChannelAgent autoNotify(this);
+
   SetMediaInfo(*aInfo);
 
   mIsEncrypted = aInfo->IsEncrypted() || mPendingEncryptedInitData.IsEncrypted();
   mTags = aTags.forget();
   mLoadedDataFired = false;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
 
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
@@ -5037,16 +4624,18 @@ void HTMLMediaElement::SeekCompleted()
   }
   FireTimeUpdate(false);
   DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
   // We changed whether we're seeking so we need to AddRemoveSelfReference
   AddRemoveSelfReference();
   if (mCurrentPlayRangeStart == -1.0) {
     mCurrentPlayRangeStart = CurrentTime();
   }
+  // Unset the variable on seekend
+  mPlayingThroughTheAudioChannelBeforeSeek = false;
 }
 
 void HTMLMediaElement::NotifySuspendedByCache(bool aIsSuspended)
 {
   mDownloadSuspendedByCache = aIsSuspended;
 }
 
 void HTMLMediaElement::DownloadSuspended()
@@ -5479,17 +5068,17 @@ bool HTMLMediaElement::CanActivateAutopl
   if (!mPaused) {
     return false;
   }
 
   if (mPausedForInactiveDocumentOrChannel) {
     return false;
   }
 
-  if (mAudioChannelWrapper && !mAudioChannelWrapper->IsAllowedToPlay()) {
+  if (!IsAllowedToPlayByAudioChannel()) {
     return false;
   }
 
   bool hasData =
     (mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
     (mSrcStream && mSrcStream->Active()) ||
     mMediaSource;
 
@@ -5902,16 +5491,18 @@ void HTMLMediaElement::AddRemoveSelfRefe
     } else {
       // Dispatch Release asynchronously so that we don't destroy this object
       // inside a call stack of method calls on this object
       nsCOMPtr<nsIRunnable> event =
         NewRunnableMethod(this, &HTMLMediaElement::DoRemoveSelfReference);
       NS_DispatchToMainThread(event);
     }
   }
+
+  UpdateAudioChannelPlayingState();
 }
 
 void HTMLMediaElement::DoRemoveSelfReference()
 {
   mShutdownObserver->ReleaseMediaElement();
 }
 
 void HTMLMediaElement::NotifyShutdownEvent()
@@ -6277,22 +5868,269 @@ NS_IMETHODIMP HTMLMediaElement::SetMozPr
 }
 
 ImageContainer* HTMLMediaElement::GetImageContainer()
 {
   VideoFrameContainer* container = GetVideoFrameContainer();
   return container ? container->GetImageContainer() : nullptr;
 }
 
+bool
+HTMLMediaElement::MaybeCreateAudioChannelAgent()
+{
+  if (mAudioChannelAgent) {
+    return true;
+  }
+
+  mAudioChannelAgent = new AudioChannelAgent();
+  nsresult rv = mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
+                                           static_cast<int32_t>(mAudioChannel),
+                                           this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mAudioChannelAgent = nullptr;
+    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+           ("HTMLMediaElement, Fail to initialize the audio channel agent,"
+            " this = %p\n", this));
+    return false;
+  }
+
+  return true;
+}
+
+bool
+HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
+{
+  // If we have an error, we are not playing.
+  if (mErrorSink->mError) {
+    return false;
+  }
+
+  // It might be resumed from remote, we should keep the audio channel agent.
+  if (IsSuspendedByAudioChannel()) {
+    return true;
+  }
+
+  // Are we paused
+  if (mPaused) {
+    return false;
+  }
+
+  // We should consider any bfcached page or inactive document as non-playing.
+  if (!IsActive()) {
+    return false;
+  }
+
+  // A loop always is playing
+  if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
+    return true;
+  }
+
+  // If we are actually playing...
+  if (IsCurrentlyPlaying()) {
+    return true;
+  }
+
+  // If we are seeking, we consider it as playing
+  if (mPlayingThroughTheAudioChannelBeforeSeek) {
+    return true;
+  }
+
+  // If we are playing an external stream.
+  if (mSrcAttrStream) {
+    return true;
+  }
+
+  return false;
+}
+
 void
 HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying)
 {
-  if (mAudioChannelWrapper) {
-    mAudioChannelWrapper->UpdateAudioChannelPlayingState(aForcePlaying);
-  }
+  bool playingThroughTheAudioChannel =
+    aForcePlaying || IsPlayingThroughTheAudioChannel();
+
+  if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
+    if (!MaybeCreateAudioChannelAgent()) {
+      return;
+    }
+
+    mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
+    NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
+  }
+}
+
+void
+HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
+{
+  if (aPlaying) {
+    // The reason we don't call NotifyStartedPlaying after the media element
+    // really becomes audible is because there is another case needs to block
+    // element as early as we can, we would hear sound leaking if we block it
+    // too late. In that case (block autoplay in non-visited-tab), we need to
+    // create a connection before decoding, because we don't want user hearing
+    // any sound.
+    AudioPlaybackConfig config;
+    nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&config,
+                                                           IsAudible());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+
+    WindowVolumeChanged(config.mVolume, config.mMuted);
+    WindowSuspendChanged(config.mSuspend);
+  } else {
+    mAudioChannelAgent->NotifyStoppedPlaying();
+  }
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::WindowVolumeChanged(float aVolume, bool 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::WindowSuspendChanged(SuspendTypes aSuspend)
+{
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("HTMLMediaElement, WindowSuspendChanged, 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:
+      SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+      Pause();
+      break;
+    default:
+      MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+             ("HTMLMediaElement, WindowSuspendChanged, 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(SuspendTypes aSuspend)
+{
+  MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
+             mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE);
+
+  SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+  nsresult rv = Play();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+  DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
+}
+
+void
+HTMLMediaElement::ResumeFromAudioChannelBlocked()
+{
+  MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
+
+  SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+  nsresult rv = Play();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+}
+
+void
+HTMLMediaElement::PauseByAudioChannel(SuspendTypes aSuspend)
+{
+  if (IsSuspendedByAudioChannel()) {
+    return;
+  }
+
+  SetAudioChannelSuspended(aSuspend);
+  Pause();
+  DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
+}
+
+void
+HTMLMediaElement::BlockByAudioChannel()
+{
+  if (IsSuspendedByAudioChannel()) {
+    return;
+  }
+
+  SetAudioChannelSuspended(nsISuspendedTypes::SUSPENDED_BLOCK);
+}
+
+void
+HTMLMediaElement::SetAudioChannelSuspended(SuspendTypes aSuspend)
+{
+  if (mAudioChannelSuspended == aSuspend) {
+    return;
+  }
+
+  MaybeNotifyMediaResumed(aSuspend);
+  mAudioChannelSuspended = aSuspend;
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("HTMLMediaElement, SetAudioChannelSuspended, this = %p, "
+          "aSuspend = %d\n", this, aSuspend));
+
+  NotifyAudioPlaybackChanged(
+    AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
+}
+
+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 &&
@@ -6303,23 +6141,47 @@ HTMLMediaElement::IsAllowedToPlay()
                                          static_cast<nsIContent*>(this),
                                          NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
                                          false,
                                          false);
 #endif
     return false;
   }
 
-  // Check our custom playback policy.
-  if (mAudioChannelWrapper) {
-    return mAudioChannelWrapper->IsAllowedToPlay();
-  }
-
-  // If the mAudioChannelWrapper doesn't exist that means the CC happened.
-  return false;
+  return IsAllowedToPlayByAudioChannel();
+}
+
+bool
+HTMLMediaElement::IsAllowedToPlayByAudioChannel()
+{
+  // The media element has already been paused or blocked, so it can't start
+  // playback again by script or user's intend until resuming by audio channel.
+  if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
+      mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
+    return false;
+  }
+
+  // If the tab hasn't been activated yet, the media element in that tab can't
+  // be playback now until the tab goes to foreground first time or user clicks
+  // the unblocking tab icon.
+  if (MaybeCreateAudioChannelAgent() && !IsTabActivated()) {
+    // Even we haven't start playing yet, we still need to notify the audio
+    // channe system because we need to receive the resume notification later.
+    UpdateAudioChannelPlayingState(true /* force to start */);
+    return false;
+  }
+
+  return true;
+}
+
+bool
+HTMLMediaElement::IsTabActivated() const
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+  return !mAudioChannelAgent->ShouldBlockMedia();
 }
 
 static const char* VisibilityString(Visibility aVisibility) {
   switch(aVisibility) {
     case Visibility::UNTRACKED: {
       return "UNTRACKED";
     }
     case Visibility::APPROXIMATELY_NONVISIBLE: {
@@ -6593,16 +6455,27 @@ HTMLMediaElement::CannotDecryptWaitingFo
     // 3. Set the media element's waiting for key value to true.
     // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
     // data enqueued in the MDSM is consumed.
     mWaitingForKey = WAITING_FOR_KEY;
     UpdateReadyStateInternal();
   }
 }
 
+NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+
+  if (mAudioCapturedByWindow != aCapture) {
+    mAudioCapturedByWindow = aCapture;
+    AudioCaptureStreamChangeIfNeeded();
+  }
+  return NS_OK;
+}
+
 AudioTrackList*
 HTMLMediaElement::AudioTracks()
 {
   if (!mAudioTrackList) {
     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(OwnerDoc()->GetParentObject());
     mAudioTrackList = new AudioTrackList(window, this);
   }
   return mAudioTrackList;
@@ -6672,31 +6545,29 @@ HTMLMediaElement::NextFrameStatus()
     return mMediaStreamListener->NextFrameStatus();
   }
   return NEXT_FRAME_UNINITIALIZED;
 }
 
 float
 HTMLMediaElement::ComputedVolume() const
 {
-  return mMuted ? 0.0f : mAudioChannelWrapper ?
-    mAudioChannelWrapper->GetEffectiveVolume() : mVolume;
+  return mMuted ? 0.0f : float(mVolume * mAudioChannelVolume);
 }
 
 bool
 HTMLMediaElement::ComputedMuted() const
 {
   return (mMuted & MUTED_BY_AUDIO_CHANNEL);
 }
 
 nsSuspendedTypes
 HTMLMediaElement::ComputedSuspended() const
 {
-  return mAudioChannelWrapper ?
-    mAudioChannelWrapper->GetSuspendType() : nsISuspendedTypes::NONE_SUSPENDED;
+  return mAudioChannelSuspended;
 }
 
 bool
 HTMLMediaElement::IsCurrentlyPlaying() const
 {
   // We have playable data, but we still need to check whether data is "real"
   // current data.
   return mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
@@ -6711,19 +6582,80 @@ HTMLMediaElement::SetAudibleState(bool a
     NotifyAudioPlaybackChanged(
       AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
   }
 }
 
 void
 HTMLMediaElement::NotifyAudioPlaybackChanged(AudibleChangedReasons aReason)
 {
-  if (mAudioChannelWrapper) {
-    mAudioChannelWrapper->NotifyAudioPlaybackChanged(aReason);
-  }
+  if (MaybeCreateAudioChannelAgent() &&
+      !mAudioChannelAgent->IsPlayingStarted()) {
+    return;
+  }
+
+  if (mAudible == IsAudible()) {
+    return;
+  }
+
+  mAudible = IsAudible();
+  mAudioChannelAgent->NotifyStartedAudible(mAudible, aReason);
+}
+
+bool
+HTMLMediaElement::IsAudible() const
+{
+  // Muted or the volume should not be ~0
+  if (Muted() || (std::fabs(Volume()) <= 1e-7)) {
+    return false;
+  }
+
+  // No sound can be heard during suspending.
+  if (IsSuspendedByAudioChannel()) {
+    return false;
+  }
+
+  // Silent audio track.
+  if (!mIsAudioTrackAudible) {
+    return false;
+  }
+
+  return true;
+}
+
+void
+HTMLMediaElement::MaybeNotifyMediaResumed(SuspendTypes aSuspend)
+{
+  // In fennec, we should send the notification when media is resumed from the
+  // pause-disposable which was called by media control.
+  if (mAudioChannelSuspended != nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE &&
+      aSuspend != nsISuspendedTypes::NONE_SUSPENDED) {
+    return;
+  }
+
+  MOZ_ASSERT(mAudioChannelAgent);
+  uint64_t windowID = mAudioChannelAgent->WindowID();
+  NS_DispatchToMainThread(NS_NewRunnableFunction([windowID]() -> void {
+    nsCOMPtr<nsIObserverService> observerService =
+      services::GetObserverService();
+    if (NS_WARN_IF(!observerService)) {
+      return;
+    }
+
+    nsCOMPtr<nsISupportsPRUint64> wrapper =
+      do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
+    if (NS_WARN_IF(!wrapper)) {
+       return;
+    }
+
+    wrapper->SetData(windowID);
+    observerService->NotifyObservers(wrapper,
+                                     "media-playback-resumed",
+                                     u"active");
+  }));
 }
 
 bool
 HTMLMediaElement::ShouldElementBePaused()
 {
   // Bfcached page or inactive document.
   if (!IsActive()) {
     return true;
@@ -6731,30 +6663,33 @@ HTMLMediaElement::ShouldElementBePaused(
 
   return false;
 }
 
 void
 HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo)
 {
   mMediaInfo = aInfo;
-  if (mAudioChannelWrapper) {
-    mAudioChannelWrapper->AudioCaptureStreamChangeIfNeeded();
-  }
+  AudioCaptureStreamChangeIfNeeded();
 }
 
 void
-HTMLMediaElement::AudioCaptureStreamChange(bool aCapture)
+HTMLMediaElement::AudioCaptureStreamChangeIfNeeded()
 {
   // No need to capture a silence media element.
   if (!HasAudio()) {
     return;
   }
 
-  if (aCapture && !mCaptureStreamPort) {
+  if (MaybeCreateAudioChannelAgent() &&
+      !mAudioChannelAgent->IsPlayingStarted()) {
+    return;
+  }
+
+  if (mAudioCapturedByWindow && !mCaptureStreamPort) {
     nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
     if (!OwnerDoc()->GetInnerWindow()) {
       return;
     }
 
     uint64_t id = window->WindowID();
     MediaStreamGraph* msg =
       MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
@@ -6762,17 +6697,17 @@ HTMLMediaElement::AudioCaptureStreamChan
 
     if (GetSrcMediaStream()) {
       mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream());
     } else {
       RefPtr<DOMMediaStream> stream =
         CaptureStreamInternal(false, false, msg);
       mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetPlaybackStream());
     }
-  } else if (!aCapture && mCaptureStreamPort) {
+  } else if (!mAudioCapturedByWindow && mCaptureStreamPort) {
     if (mDecoder) {
       ProcessedMediaStream* ps =
         mCaptureStreamPort->GetSource()->AsProcessedStream();
       MOZ_ASSERT(ps);
 
       for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
         if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
           mOutputStreams.RemoveElementAt(i);
@@ -6852,19 +6787,10 @@ HTMLMediaElement::MarkAsContentSource(Ca
     }
   }
 
   LOG(LogLevel::Debug,
       ("%p Log VIDEO_AS_CONTENT_SOURCE: visibility = %u, API: '%d' and 'All'",
        this, isVisible, aAPI));
 }
 
-void
-HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
-{
-  OpenUnsupportedMediaWithExternalAppIfNeeded();
-  if (mAudioChannelWrapper) {
-    mAudioChannelWrapper->NotifyPlayStarted();
-  }
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -43,26 +43,28 @@ typedef uint32_t AudibleChangedReasons;
 namespace mozilla {
 class DecoderDoctorDiagnostics;
 class DOMMediaStream;
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
+class AudioChannelAgent;
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
 class MediaTrack;
 class MediaStreamTrack;
 class VideoStreamTrack;
 } // namespace dom
 } // namespace mozilla
 
+class AutoNotifyAudioChannelAgent;
 class nsIChannel;
 class nsIHttpChannel;
 class nsILoadGroup;
 class nsIRunnable;
 class nsITimer;
 class nsRange;
 
 namespace mozilla {
@@ -75,19 +77,22 @@ class MediaError;
 class MediaSource;
 class TextTrackList;
 class AudioTrackList;
 class VideoTrackList;
 
 class HTMLMediaElement : public nsGenericHTMLElement,
                          public nsIDOMHTMLMediaElement,
                          public MediaDecoderOwner,
+                         public nsIAudioChannelAgentCallback,
                          public PrincipalChangeObserver<DOMMediaStream>,
                          public SupportsWeakPtr<HTMLMediaElement>
 {
+  friend AutoNotifyAudioChannelAgent;
+
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
   typedef mozilla::MediaStream MediaStream;
   typedef mozilla::MediaResource MediaResource;
   typedef mozilla::MediaDecoderOwner MediaDecoderOwner;
   typedef mozilla::MetadataTags MetadataTags;
@@ -108,16 +113,18 @@ public:
    * @param aListener returns a stream listener that should receive
    * notifications for the stream
    */
   nsresult LoadWithChannel(nsIChannel *aChannel, nsIStreamListener **aListener);
 
   // nsIDOMHTMLMediaElement
   NS_DECL_NSIDOMHTMLMEDIAELEMENT
 
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
+
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLMediaElement,
                                            nsGenericHTMLElement)
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
                               const nsAString& aValue,
@@ -745,17 +752,16 @@ public:
     CREATE_IMAGEBITMAP,
     CAPTURE_STREAM,
   };
   void MarkAsContentSource(CallerAPI aAPI);
 
 protected:
   virtual ~HTMLMediaElement();
 
-  class AudioChannelAgentCallback;
   class ChannelLoader;
   class ErrorSink;
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
   class MediaStreamTrackListener;
   class StreamListener;
   class StreamSizeListener;
   class ShutdownObserver;
@@ -1195,45 +1201,95 @@ protected:
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
   // 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.
   already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
 
+  // A method to check if we are playing through the AudioChannel.
+  bool IsPlayingThroughTheAudioChannel() const;
+
   // Update the audio channel playing state
   void UpdateAudioChannelPlayingState(bool aForcePlaying = false);
 
   // Adds to the element's list of pending text tracks each text track
   // in the element's list of text tracks whose text track mode is not disabled
   // and whose text track readiness state is loading.
   void PopulatePendingTextTrackList();
 
   // Gets a reference to the MediaElement's TextTrackManager. If the
   // MediaElement doesn't yet have one then it will create it.
   TextTrackManager* GetOrCreateTextTrackManager();
 
   // Recomputes ready state and fires events as necessary based on current state.
   void UpdateReadyStateInternal();
 
+  // Notifies the audio channel agent when the element starts or stops playing.
+  void NotifyAudioChannelAgent(bool aPlaying);
+
+  // True if we create the audio channel agent successfully or we already have
+  // one. The agent is used to communicate with the AudioChannelService. eg.
+  // notify we are playing/audible and receive muted/unmuted/suspend/resume
+  // commands from AudioChannelService.
+  bool MaybeCreateAudioChannelAgent();
+
   // Determine if the element should be paused because of suspend conditions.
   bool ShouldElementBePaused();
 
-  // Create or destroy the captured stream.
-  void AudioCaptureStreamChange(bool aCapture);
+  // Create or destroy the captured stream depend on mAudioCapturedByWindow.
+  void AudioCaptureStreamChangeIfNeeded();
+
+  /**
+   * 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(SuspendTypes aSuspend);
+  void BlockByAudioChannel();
+
+  void ResumeFromAudioChannel();
+  void ResumeFromAudioChannelPaused(SuspendTypes aSuspend);
+  void ResumeFromAudioChannelBlocked();
+
+  bool IsSuspendedByAudioChannel() const;
+  void SetAudioChannelSuspended(SuspendTypes aSuspend);
 
   // A method to check whether the media element is allowed to start playback.
   bool IsAllowedToPlay();
+  bool IsAllowedToPlayByAudioChannel();
 
   // If the network state is empty and then we would trigger DoLoad().
   void MaybeDoLoad();
 
-  // Anything we need to check after played success and not related with spec.
-  void UpdateCustomPolicyAfterPlayed();
+  // True if the tab which media element belongs to has been to foreground at
+  // least once or activated by manually clicking the unblocking tab icon.
+  bool IsTabActivated() const;
+
+  bool IsAudible() const;
+
+  // It's used for fennec only, send the notification when the user resumes the
+  // media which was paused by media control.
+  void MaybeNotifyMediaResumed(SuspendTypes aSuspend);
 
   class nsAsyncEventRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // Open unsupported types media with the external app when the media element
   // triggers play() after loaded fail. eg. preload the data before start play.
@@ -1445,38 +1501,46 @@ 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;
+  SuspendTypes 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.)
   // True if casting is currently allowed
   bool mAllowCasting;
   // True if currently casting this video
   bool mIsCasting;
 
   // True if the sound is being captured.
   bool mAudioCaptured;
 
+  // True if the sound is being captured by the window.
+  bool mAudioCapturedByWindow;
+
   // If TRUE then the media element was actively playing before the currently
   // in progress seeking. If FALSE then the media element is either not seeking
   // or was not actively playing before the current seek. Used to decide whether
   // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
   bool mPlayingBeforeSeek;
 
+  // if TRUE then the seek started while content was in active playing state
+  // if FALSE then the seek started while the content was not playing.
+  bool mPlayingThroughTheAudioChannelBeforeSeek;
+
   // True iff this element is paused because the document is inactive or has
   // been suspended by the audio channel service.
   bool mPausedForInactiveDocumentOrChannel;
 
   // True iff event delivery is suspended (mPausedForInactiveDocumentOrChannel must also be true).
   bool mEventDeliveryPaused;
 
   // True if we're running the "load()" method.
@@ -1562,21 +1626,31 @@ protected:
   EncryptionInfo mPendingEncryptedInitData;
 
   // True if the media's channel's download has been suspended.
   Watchable<bool> mDownloadSuspendedByCache;
 
   // Audio Channel.
   AudioChannel mAudioChannel;
 
+  // The audio channel volume
+  float mAudioChannelVolume;
+
+  // Is this media element playing?
+  bool mPlayingThroughTheAudioChannel;
+
   // Disable the video playback by track selection. This flag might not be
   // enough if we ever expand the ability of supporting multi-tracks video
   // playback.
   bool mDisableVideo;
 
+  // An agent used to join audio channel service and its life cycle would equal
+  // to media element.
+  RefPtr<AudioChannelAgent> mAudioChannelAgent;
+
   RefPtr<TextTrackManager> mTextTrackManager;
 
   RefPtr<AudioTrackList> mAudioTrackList;
 
   RefPtr<VideoTrackList> mVideoTrackList;
 
   nsAutoPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
 
@@ -1658,22 +1732,20 @@ private:
   // Media elements also have a default playback start position, which must
   // initially be set to zero seconds. This time is used to allow the element to
   // be seeked even before the media is loaded.
   double mDefaultPlaybackStartPosition;
 
   // True if the audio track is not silent.
   bool mIsAudioTrackAudible;
 
+  // True if media element is audible for users.
+  bool mAudible;
+
   Visibility mVisibilityState;
 
   UniquePtr<ErrorSink> mErrorSink;
-
-  // This wrapper will handle all audio channel related stuffs, eg. the operations
-  // of tab audio indicator, Fennec's media control.
-  // Note: mAudioChannelWrapper might be null after GC happened.
-  RefPtr<AudioChannelAgentCallback> mAudioChannelWrapper;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h