Bug 1274919 - part2 : implement resume/suspend mechanism in MediaDecoder. draft
authorAlastor Wu <alwu@mozilla.com>
Fri, 14 Jul 2017 16:19:45 +0800
changeset 608869 768056fd058dafe99e5dd99abdac98c7c35f5c78
parent 608868 c6979124cce1cc023ef256581138a999de3bd6d0
child 608870 94b24ee236da226f8170660e925cb3e14b007bed
child 608875 f855331a687b7ce9eb7ebadecaa9966d7056e75f
push id68429
push useralwu@mozilla.com
push dateFri, 14 Jul 2017 08:20:09 +0000
bugs1274919
milestone56.0a1
Bug 1274919 - part2 : implement resume/suspend mechanism in MediaDecoder. Create the new class "BackgroundVideoDecodingPermissionObserver" to handle the suspended request sent from the front end side. When the tab goes to background and the video can be suspended, we would start the observer. When the tab goes to foreground and the video won't be suspended anymore, we would stop the observer. MozReview-Commit-ID: AQAog6QRXCj
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -121,16 +121,139 @@ public:
     DecodersArray& decoders = Decoders();
     decoders.RemoveElement(aDecoder);
     if (decoders.IsEmpty()) {
       sUniqueInstance = nullptr;
     }
   }
 };
 
+class MediaDecoder::BackgroundVideoDecodingPermissionObserver final :
+  public nsIObserver
+{
+  public:
+    NS_DECL_ISUPPORTS
+
+    explicit BackgroundVideoDecodingPermissionObserver(MediaDecoder* aDecoder)
+      : mDecoder(aDecoder)
+      , mIsRegisteredForEvent(false)
+    {
+      MOZ_ASSERT(mDecoder);
+    }
+
+    NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                       const char16_t* aData) override
+    {
+      if (!IsValidEventSender(aSubject)) {
+        return NS_OK;
+      }
+
+      if (strcmp(aTopic, "background-video-decoding") == 0) {
+        mDecoder->mIsBackgroundVideoDecodingAllowed = !NS_strcmp(aData, u"resume");;
+        mDecoder->UpdateVideoDecodeMode();
+      }
+      return NS_OK;
+    }
+
+    void UpdateRegistration() {
+      MOZ_ASSERT(mDecoder);
+      // If the docuement is visible, always unregister event. If the docuement
+      // is invisibe, depend on whether the decoding could be suspended.
+      bool shouldNeverBeSuspended = mDecoder->mHasSuspendTaint ||
+                                   !mDecoder->mIsElementInTree;
+      if (IsRegistered()) {
+        if (mDecoder->mIsDocumentVisible ||
+            shouldNeverBeSuspended) {
+          UnregisterEvent();
+        }
+      } else {
+        if (!mDecoder->mIsDocumentVisible &&
+            !shouldNeverBeSuspended) {
+          RegisterEvent();
+        }
+      }
+    }
+
+    void Shutdown() {
+      if (IsRegistered()) {
+        UnregisterEvent();
+      }
+    }
+  private:
+    ~BackgroundVideoDecodingPermissionObserver() {
+      MOZ_ASSERT(!mIsRegisteredForEvent);
+    }
+
+    bool IsRegistered() const {
+      return mIsRegisteredForEvent;
+    }
+
+    void RegisterEvent() {
+      MOZ_ASSERT(!mIsRegisteredForEvent);
+      nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+      if (observerService) {
+        observerService->AddObserver(this, "background-video-decoding", false);
+        mIsRegisteredForEvent = true;
+      }
+    }
+
+    void UnregisterEvent() {
+      MOZ_ASSERT(mIsRegisteredForEvent);
+      nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+      if (observerService) {
+        observerService->RemoveObserver(this, "background-video-decoding");
+        mIsRegisteredForEvent = false;
+        mDecoder->mIsBackgroundVideoDecodingAllowed = false;
+        mDecoder->UpdateVideoDecodeMode();
+      }
+    }
+
+    bool IsValidEventSender(nsISupports* aSubject) const
+    {
+      nsCOMPtr<nsPIDOMWindowInner> senderInner(do_QueryInterface(aSubject));
+      if (!senderInner) {
+        return false;
+      }
+
+      nsCOMPtr<nsPIDOMWindowOuter> senderOuter = senderInner->GetOuterWindow();
+      if (!senderOuter) {
+        return false;
+      }
+
+      nsCOMPtr<nsPIDOMWindowOuter> senderTop = senderOuter->GetTop();
+      if (!senderTop) {
+        return false;
+      }
+
+      nsIDocument* doc = mDecoder->GetOwner()->GetDocument();
+      if (!doc) {
+        return false;
+      }
+
+      nsCOMPtr<nsPIDOMWindowInner> ownerInner = doc->GetInnerWindow();
+      if (!ownerInner) {
+        return false;
+      }
+
+      nsCOMPtr<nsPIDOMWindowOuter> ownerOuter = ownerInner->GetOuterWindow();
+      if (!ownerOuter) {
+        return false;
+      }
+
+      nsCOMPtr<nsPIDOMWindowOuter> ownerTop = ownerOuter->GetTop();
+      return ownerTop == senderTop;
+    }
+    // The life cycle of observer would always be shorter than decoder, so we
+    // use raw pointer here.
+    MediaDecoder* mDecoder;
+    bool mIsRegisteredForEvent;
+};
+
+NS_IMPL_ISUPPORTS(MediaDecoder::BackgroundVideoDecodingPermissionObserver, nsIObserver)
+
 StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
 
 LazyLogModule gMediaTimerLog("MediaTimer");
 
 constexpr TimeUnit MediaDecoder::DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED;
 
 void
 MediaDecoder::InitStatics()
@@ -259,16 +382,18 @@ MediaDecoder::MediaDecoder(MediaDecoderI
   , INIT_CANONICAL(mPlayState, PLAY_STATE_LOADING)
   , INIT_CANONICAL(mNextState, PLAY_STATE_PAUSED)
   , INIT_CANONICAL(mLogicallySeeking, false)
   , INIT_CANONICAL(mSameOriginMedia, false)
   , INIT_CANONICAL(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE)
   , INIT_CANONICAL(mPlaybackBytesPerSecond, 0.0)
   , INIT_CANONICAL(mPlaybackRateReliable, true)
   , INIT_CANONICAL(mDecoderPosition, 0)
+  , mVideoDecodingOberver(new BackgroundVideoDecodingPermissionObserver(this))
+  , mIsBackgroundVideoDecodingAllowed(false)
   , mTelemetryReported(false)
   , mIsMediaElement(!!mOwner->GetMediaElement())
   , mElement(mOwner->GetMediaElement())
   , mContainerType(aInit.mContainerType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mAbstractMainThread);
   MediaMemoryTracker::AddMediaDecoder(this);
@@ -350,16 +475,18 @@ MediaDecoder::Shutdown()
   if (mResource) {
     mResource->Close();
   }
 
   // Ask the owner to remove its audio/video tracks.
   GetOwner()->RemoveMediaTracks();
 
   ChangeState(PLAY_STATE_SHUTDOWN);
+  mVideoDecodingOberver->Shutdown();
+  mVideoDecodingOberver = nullptr;
   mOwner = nullptr;
 }
 
 void
 MediaDecoder::NotifyXPCOMShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (auto owner = GetOwner()) {
@@ -1086,32 +1213,34 @@ void
 MediaDecoder::SetElementVisibility(bool aIsDocumentVisible,
                                    Visibility aElementVisibility,
                                    bool aIsElementInTree)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mIsDocumentVisible = aIsDocumentVisible;
   mElementVisibility = aElementVisibility;
   mIsElementInTree = aIsElementInTree;
+  mVideoDecodingOberver->UpdateRegistration();
   UpdateVideoDecodeMode();
 }
 
 void
 MediaDecoder::SetForcedHidden(bool aForcedHidden)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mForcedHidden = aForcedHidden;
   UpdateVideoDecodeMode();
 }
 
 void
 MediaDecoder::SetSuspendTaint(bool aTainted)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mHasSuspendTaint = aTainted;
+  mVideoDecodingOberver->UpdateRegistration();
   UpdateVideoDecodeMode();
 }
 
 void
 MediaDecoder::UpdateVideoDecodeMode()
 {
   // The MDSM may yet be set.
   if (!mDecoderStateMachine) {
@@ -1137,16 +1266,22 @@ MediaDecoder::UpdateVideoDecodeMode()
   }
 
   // If mForcedHidden is set, suspend the video decoder anyway.
   if (mForcedHidden) {
     mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
     return;
   }
 
+  // Resume decoding in the advance, even the element is in the background.
+  if (mIsBackgroundVideoDecodingAllowed) {
+    mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
+    return;
+  }
+
   // Otherwise, depends on the owner's visibility state.
   // A element is visible only if its document is visible and the element
   // itself is visible.
   if (mIsDocumentVisible &&
       mElementVisibility == Visibility::APPROXIMATELY_VISIBLE) {
     mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
   } else {
     mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -779,16 +779,26 @@ protected:
   Canonical<bool> mPlaybackRateReliable;
 
   // Current decoding position in the stream. This is where the decoder
   // is up to consuming the stream. This is not adjusted during decoder
   // seek operations, but it's updated at the end when we start playing
   // back again.
   Canonical<int64_t> mDecoderPosition;
 
+  // When the video decoding is suspended due to the media element is in the
+  // background, we might temporary resume the video decoding in some situation
+  // and this observer is used to listen the related events.
+  class BackgroundVideoDecodingPermissionObserver;
+  RefPtr<BackgroundVideoDecodingPermissionObserver> mVideoDecodingOberver;
+
+  // True if we want to resume video decoding even the media element is in the
+  // background.
+  bool mIsBackgroundVideoDecodingAllowed;
+
 public:
   AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
   AbstractCanonical<double>* CanonicalVolume() { return &mVolume; }
   AbstractCanonical<bool>* CanonicalPreservesPitch()
   {
     return &mPreservesPitch;
   }
   AbstractCanonical<bool>* CanonicalLooping()