Bug 1302350 - part4 : refactor the media-blocking mechanism. draft
authorAlastor Wu <alwu@mozilla.com>
Wed, 02 Nov 2016 14:22:24 +0800
changeset 432490 1c7768bf57da1468dfd69e391aaff5d10ef8bd69
parent 432489 9b11baae552f4b654c4c5d7c10d83477ad6e323a
child 432491 a635e5212548e657cc7f9b1e577f0d81c16d373b
push id34328
push useralwu@mozilla.com
push dateWed, 02 Nov 2016 06:23:00 +0000
bugs1302350
milestone52.0a1
Bug 1302350 - part4 : refactor the media-blocking mechanism. 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
@@ -2996,29 +2996,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();
@@ -3085,16 +3085,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;
@@ -4941,16 +4945,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 || mMediaSource;
 
   return hasData;
 }
 
 void HTMLMediaElement::CheckAutoplayDataReady()
@@ -4965,19 +4973,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"));
@@ -5769,19 +5775,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
@@ -5889,31 +5896,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;
   }
@@ -5926,18 +5935,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;
   }
@@ -5974,25 +5981,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";
     }
@@ -6494,21 +6518,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
@@ -1200,17 +1200,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.
@@ -1257,18 +1257,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);