Bug 1302350 - part4 : refactor the media-blocking mechanism. r=jwwang
authorAlastor Wu <alwu@mozilla.com>
Wed, 02 Nov 2016 14:22:24 +0800
changeset 347484 4cdb9e2c9595ff669db771964feae1e0242698d6
parent 347483 ddf60739dfd73938346d0146738f38d8802dd226
child 347485 d94c3a6c1ad6785940c861225b6394c9e51ca3d4
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwwang
bugs1302350
milestone52.0a1
Bug 1302350 - part4 : refactor the media-blocking mechanism. r=jwwang The old way is to start playing first, and then block the media element. This way is too complicated because it involves lots of interal state and isn't intuitive. The new way is to ignore the play if the media element should be blocked. It's easy to know and we doesn't need to keep any internal states because we don't play the media element. MozReview-Commit-ID: B20e0pvXES4
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3025,29 +3025,29 @@ void
 HTMLMediaElement::NotifyXPCOMShutdown()
 {
   ShutdownDecoder();
 }
 
 void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
+  if (!IsAllowedToPlay()) {
+    return;
+  }
+
   nsresult rv = PlayInternal();
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
 nsresult
 HTMLMediaElement::PlayInternal()
 {
-  if (!IsAllowedToPlay()) {
-    return NS_OK;
-  }
-
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     DoLoad();
@@ -3114,16 +3114,20 @@ HTMLMediaElement::PlayInternal()
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP HTMLMediaElement::Play()
 {
+  if (!IsAllowedToPlay()) {
+    return NS_OK;
+  }
+
   return PlayInternal();
 }
 
 HTMLMediaElement::WakeLockBoolWrapper&
 HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val)
 {
   if (mValue == val) {
     return *this;
@@ -4974,16 +4978,20 @@ bool HTMLMediaElement::CanActivateAutopl
   if (!mPaused) {
     return false;
   }
 
   if (mPausedForInactiveDocumentOrChannel) {
     return false;
   }
 
+  if (!IsAllowedToPlay()) {
+    return false;
+  }
+
   bool hasData =
     (mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
     (mSrcStream && mSrcStream->Active()) ||
     mMediaSource;
 
   return hasData;
 }
 
@@ -4999,19 +5007,17 @@ void HTMLMediaElement::CheckAutoplayData
   UpdateSrcMediaStreamPlaying();
   UpdateAudioChannelPlayingState();
 
   if (mDecoder) {
     SetPlayedOrSeeked(true);
     if (mCurrentPlayRangeStart == -1.0) {
       mCurrentPlayRangeStart = CurrentTime();
     }
-    if (!ShouldElementBePaused()) {
-      mDecoder->Play();
-    }
+    mDecoder->Play();
   } else if (mSrcStream) {
     SetPlayedOrSeeked(true);
   }
 
   // For blocked media, the event would be pending until it is resumed.
   DispatchAsyncEvent(NS_LITERAL_STRING("play"));
 
   DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
@@ -5803,19 +5809,20 @@ HTMLMediaElement::IsPlayingThroughTheAud
   if (mSrcAttrStream) {
     return true;
   }
 
   return false;
 }
 
 void
-HTMLMediaElement::UpdateAudioChannelPlayingState()
-{
-  bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
+HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying)
+{
+  bool playingThroughTheAudioChannel =
+    aForcePlaying || IsPlayingThroughTheAudioChannel();
 
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
     NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
   }
 }
 
 void
@@ -5923,31 +5930,33 @@ HTMLMediaElement::ResumeFromAudioChannel
 
 void
 HTMLMediaElement::ResumeFromAudioChannelPaused(SuspendTypes aSuspend)
 {
   MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
              mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE);
 
   SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
-  nsresult rv = PlayInternal();
+  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);
-  mPaused = false;
-  SuspendOrResumeElement(false /* resume */, false);
+  nsresult rv = Play();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 }
 
 void
 HTMLMediaElement::PauseByAudioChannel(SuspendTypes aSuspend)
 {
   if (IsSuspendedByAudioChannel()) {
     return;
   }
@@ -5960,18 +5969,16 @@ HTMLMediaElement::PauseByAudioChannel(Su
 void
 HTMLMediaElement::BlockByAudioChannel()
 {
   if (IsSuspendedByAudioChannel()) {
     return;
   }
 
   SetAudioChannelSuspended(nsISuspendedTypes::SUSPENDED_BLOCK);
-  mPaused = true;
-  SuspendOrResumeElement(true /* suspend */, true /* pending event */);
 }
 
 void
 HTMLMediaElement::SetAudioChannelSuspended(SuspendTypes aSuspend)
 {
   if (mAudioChannelSuspended == aSuspend) {
     return;
   }
@@ -6008,25 +6015,42 @@ HTMLMediaElement::IsAllowedToPlay()
                                          static_cast<nsIContent*>(this),
                                          NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
                                          false,
                                          false);
 #endif
     return false;
   }
 
-  // The MediaElement can't start playback until it's resumed by audio channel.
+  // 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 (!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
+{
+  return !mAudioChannelAgent->ShouldBlockMedia();
+}
+
 static const char* VisibilityString(Visibility aVisibility) {
   switch(aVisibility) {
     case Visibility::UNTRACKED: {
       return "UNTRACKED";
     }
     case Visibility::APPROXIMATELY_NONVISIBLE: {
       return "APPROXIMATELY_NONVISIBLE";
     }
@@ -6528,21 +6552,16 @@ HTMLMediaElement::OpenUnsupportedMediaWi
                                        NS_LITERAL_STRING("OpenMediaWithExternalApp"),
                                        true,
                                        true);
 }
 
 bool
 HTMLMediaElement::ShouldElementBePaused()
 {
-  // The media in the non-visited page would be blocked.
-  if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
-    return true;
-  }
-
   // Bfcached page or inactive document.
   if (!IsActive()) {
     return true;
   }
 
   return false;
 }
 
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1207,17 +1207,17 @@ protected:
   // 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();
+  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.
@@ -1264,18 +1264,23 @@ protected:
 
   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();
 
+  // 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;
   bool HaveFailedWithSourceNotSupportedError() const;
 
   void OpenUnsupportedMediaWithExtenalAppIfNeeded();
 
   // 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);