Bug 1275481: Suspend video decoding via timer. r=jwwang
authorDan Glastonbury <dglastonbury@mozilla.com>
Mon, 30 May 2016 17:25:10 +1000
changeset 300254 3206ef153b98d1200aab86f50a27d884509d39e1
parent 300253 8374debdc9336ce898c41353bd4ae5be0189dbb1
child 300255 e081fc6f3f9073d4e53c2e1917665670ec1186ba
push id77880
push userdglastonbury@mozilla.com
push dateFri, 03 Jun 2016 01:54:55 +0000
treeherdermozilla-inbound@3206ef153b98 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwwang
bugs1275481
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1275481: Suspend video decoding via timer. r=jwwang Suspend decoding of video in background tabs once timer fires. The timer is trigger on switch to invisible and reset each time becomes visible. Default timer is set at 10 seconds and is controlled via pref media.suspend-bkgnd-video.delay-ms. MozReview-Commit-ID: IPIQ1g7Hkpw
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaPrefs.h
modules/libpref/init/all.js
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -41,16 +41,17 @@
 #include "AudioSegment.h"
 #include "DOMMediaStream.h"
 #include "ImageContainer.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderReaderWrapper.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaShutdownManager.h"
+#include "MediaPrefs.h"
 #include "MediaTimer.h"
 #include "NextFrameSeekTask.h"
 #include "TimeUnits.h"
 #include "VideoSegment.h"
 #include "VideoUtils.h"
 #include "gfxPrefs.h"
 
 namespace mozilla {
@@ -198,29 +199,27 @@ static void InitVideoQueuePrefs() {
       "media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE);
     sVideoQueueHWAccelSize = Preferences::GetUint(
       "media.video-queue.hw-accel-size", HW_VIDEO_QUEUE_SIZE);
     sVideoQueueSendToCompositorSize = Preferences::GetUint(
       "media.video-queue.send-to-compositor-size", VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE);
   }
 }
 
-static bool sSuspendBackgroundVideos = true;
+// Delay, in milliseconds, that tabs needs to be in background before video
+// decoding is suspended.
+static TimeDuration sSuspendBackgroundVideoDelay;
 
 static void
 InitSuspendBackgroundPref()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
 
-  static bool sSetupPrefCache = false;
-  if (!sSetupPrefCache) {
-    sSetupPrefCache = true;
-    Preferences::AddBoolVarCache(&sSuspendBackgroundVideos,
-                                 "media.suspend-bkgnd-video.enabled", false);
-  }
+  sSuspendBackgroundVideoDelay = TimeDuration::FromMilliseconds(
+      MediaPrefs::MDSMSuspendBackgroundVideoDelay());
 }
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoderID(aDecoder),
   mFrameStats(&aDecoder->GetFrameStatistics()),
   mVideoFrameContainer(aDecoder->GetVideoFrameContainer()),
@@ -251,16 +250,18 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDispatchedEventToDecode(false),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
   mDecodingFirstFrame(true),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false),
   mSentPlaybackEndedEvent(false),
+  mVideoDecodeSuspended(false),
+  mVideoDecodeSuspendTimer(mTaskQueue),
   mOutputStreamManager(new OutputStreamManager()),
   mResource(aDecoder->GetResource()),
   mAudioOffloading(false),
   mBuffered(mTaskQueue, TimeIntervals(),
             "MediaDecoderStateMachine::mBuffered (Mirror)"),
   mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
                     "MediaDecoderStateMachine::mEstimatedDuration (Mirror)"),
   mExplicitDuration(mTaskQueue, Maybe<double>(),
@@ -1221,16 +1222,20 @@ MediaDecoderStateMachine::Shutdown()
   SetState(DECODER_STATE_SHUTDOWN);
 
   mBufferedUpdateRequest.DisconnectIfExists();
 
   mQueuedSeek.RejectIfExists(__func__);
 
   DiscardSeekTaskIfExist();
 
+  // Shutdown happens will decode timer is active, we need to disconnect and
+  // dispose of the timer.
+  mVideoDecodeSuspendTimer.Reset();
+
 #ifdef MOZ_EME
   mCDMProxyPromise.DisconnectIfExists();
 #endif
 
   if (IsPlaying()) {
     StopPlayback();
   }
 
@@ -1296,20 +1301,18 @@ void MediaDecoderStateMachine::StartDeco
 
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::PlayStateChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  // This method used to be a Play() method invoked by MediaDecoder when the
-  // play state became PLAY_STATE_PLAYING. As such, it doesn't have any work to
-  // do for other state changes. That could change.
   if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
+    mVideoDecodeSuspendTimer.Reset();
     return;
   }
 
   // Once we start playing, we don't want to minimize our prerolling, as we
   // assume the user is likely to want to keep playing in future. This needs to
   // happen before we invoke StartDecoding().
   if (mMinimizePreroll) {
     mMinimizePreroll = false;
@@ -1338,41 +1341,61 @@ void MediaDecoderStateMachine::PlayState
   }
 
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::VisibilityChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
-  DECODER_LOG("VisibilityChanged: is visible = %d", mIsVisible.Ref());
+  DECODER_LOG("VisibilityChanged: is visible = %d, video decode suspended = %d",
+              mIsVisible.Ref(), mVideoDecodeSuspended);
 
   // Not suspending background videos so there's nothing to do.
-  if (!sSuspendBackgroundVideos) {
+  if (!MediaPrefs::MDSMSuspendBackgroundVideoEnabled()) {
     return;
   }
 
   if (!HasVideo()) {
     return;
   }
 
-  // If not transitioning to visible and not playing then there's
-  // nothing to do.
-  if (!mIsVisible || mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
+  // If not playing then there's nothing to do.
+  if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
+    return;
+  }
+
+  // Start timer to trigger suspended decoding state when going invisible.
+  if (!mIsVisible) {
+    TimeStamp target = TimeStamp::Now() + sSuspendBackgroundVideoDelay;
+
+    RefPtr<MediaDecoderStateMachine> self = this;
+    mVideoDecodeSuspendTimer.Ensure(target,
+                                    [=]() { self->OnSuspendTimerResolved(); },
+                                    [=]() { self->OnSuspendTimerRejected(); });
     return;
   }
 
-  // If an existing seek is in flight don't bother creating a new one to catch
-  // up.
-  if (mSeekTask || mQueuedSeek.Exists()) {
-    return;
+  // Resuming from suspended decoding
+
+  // If suspend timer exists, destroy it.
+  mVideoDecodeSuspendTimer.Reset();
+
+  if (mVideoDecodeSuspended) {
+    mVideoDecodeSuspended = false;
+
+    // If an existing seek is in flight don't bother creating a new
+    // one to catch up.
+    if (mSeekTask || mQueuedSeek.Exists()) {
+      return;
+    }
+
+    // Start video-only seek to the current time...
+    InitiateVideoDecodeRecoverySeek();
   }
-
-  // Start video-only seek to the current time...
-  InitiateVideoDecodeRecoverySeek();
 }
 
 // InitiateVideoDecodeRecoverySeek is responsible for setting up a video-only
 // seek using the seek task. When suspension of decoding for videos that are in
 // background tabs (ie. invisible) is enabled, the audio keeps playing and when
 // switching back to decoding video, it is highly desirable to not cause the
 // audio to pause as the video is seeked else there be a noticeable audio glitch
 // as the tab becomes visible.
@@ -2600,17 +2623,17 @@ bool MediaDecoderStateMachine::IsStateMa
 {
   MOZ_ASSERT(OnTaskQueue());
   return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
 }
 
 bool MediaDecoderStateMachine::IsVideoDecodeSuspended() const
 {
   MOZ_ASSERT(OnTaskQueue());
-  return sSuspendBackgroundVideos && !mIsVisible;
+  return MediaPrefs::MDSMSuspendBackgroundVideoEnabled() && mVideoDecodeSuspended;
 }
 
 void
 MediaDecoderStateMachine::LogicalPlaybackRateChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (mLogicalPlaybackRate == 0) {
@@ -2886,16 +2909,33 @@ MediaDecoderStateMachine::VideoRequestSt
     MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingVideoData());
     return "pending";
   } else if (mReader->IsWaitingVideoData()) {
     return "waiting";
   }
   return "idle";
 }
 
+void
+MediaDecoderStateMachine::OnSuspendTimerResolved()
+{
+  DECODER_LOG("OnSuspendTimerResolved");
+  mVideoDecodeSuspendTimer.CompleteRequest();
+  mVideoDecodeSuspended = true;
+}
+
+void
+MediaDecoderStateMachine::OnSuspendTimerRejected()
+{
+  DECODER_LOG("OnSuspendTimerRejected");
+  MOZ_ASSERT(OnTaskQueue());
+  MOZ_ASSERT(!mVideoDecodeSuspended);
+  mVideoDecodeSuspendTimer.CompleteRequest();
+}
+
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 #undef DECODER_WARN
 #undef DECODER_WARN_HELPER
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -832,16 +832,19 @@ private:
   CallbackID mAudioCallbackID;
   CallbackID mWaitAudioCallbackID;
   const char* AudioRequestStatus() const;
 
   CallbackID mVideoCallbackID;
   CallbackID mWaitVideoCallbackID;
   const char* VideoRequestStatus() const;
 
+  void OnSuspendTimerResolved();
+  void OnSuspendTimerRejected();
+
   // True if we shouldn't play our audio (but still write it to any capturing
   // streams). When this is true, the audio thread will never start again after
   // it has stopped.
   bool mAudioCaptured;
 
   // True if the audio playback thread has finished. It is finished
   // when either all the audio frames have completed playing, or we've moved
   // into shutdown state, and the threads are to be
@@ -919,16 +922,22 @@ private:
   // FirstFrameLoadedEvent was already sent, then we can skip
   // SetStartTime because the mStartTime already set before. Also we don't need
   // to decode any audio/video since the MediaDecoder will trigger a seek
   // operation soon.
   bool mSentFirstFrameLoadedEvent;
 
   bool mSentPlaybackEndedEvent;
 
+  // True if video decoding is suspended.
+  bool mVideoDecodeSuspended;
+
+  // Track enabling video decode suspension via timer
+  DelayedScheduler mVideoDecodeSuspendTimer;
+
   // Data about MediaStreams that are being fed by the decoder.
   const RefPtr<OutputStreamManager> mOutputStreamManager;
 
   // Media data resource from the decoder.
   RefPtr<MediaResource> mResource;
 
   // Track the complete & error for audio/video separately
   MozPromiseRequestHolder<GenericPromise> mMediaSinkAudioPromise;
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -29,16 +29,17 @@ public:                                 
 static const Type& Name() { MOZ_ASSERT(SingletonExists()); return GetSingleton().mPref##Name.mValue; } \
 private:                                                                      \
 static const char* Get##Name##PrefName() { return Pref; }                     \
 static Type Get##Name##PrefDefault() { return Default; }                      \
 PrefTemplate<Type, Get##Name##PrefDefault, Get##Name##PrefName> mPref##Name
 
 // Custom Definitions.
 #define GMP_DEFAULT_ASYNC_SHUTDOWN_TIMEOUT 3000
+#define SUSPEND_BACKGROUND_VIDEO_DELAY_MS 10000
 #define TEST_PREFERENCE_FAKE_RECOGNITION_SERVICE "media.webspeech.test.fake_recognition_service"
 
 namespace mozilla {
 
 template<class T> class StaticAutoPtr;
 
 class MediaPrefs final
 {
@@ -101,16 +102,20 @@ private:
 #endif
   DECL_MEDIA_PREF("media.decoder.fuzzing.enabled",            PDMFuzzingEnabled, bool, false);
   DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0);
   DECL_MEDIA_PREF("media.decoder.fuzzing.dont-delay-inputexhausted", PDMFuzzingDelayInputExhausted, bool, true);
   DECL_MEDIA_PREF("media.gmp.decoder.enabled",                PDMGMPEnabled, bool, true);
   DECL_MEDIA_PREF("media.gmp.decoder.aac",                    GMPAACPreferred, uint32_t, 0);
   DECL_MEDIA_PREF("media.gmp.decoder.h264",                   GMPH264Preferred, uint32_t, 0);
 
+  // MediaDecoderStateMachine
+  DECL_MEDIA_PREF("media.suspend-bkgnd-video.enabled",        MDSMSuspendBackgroundVideoEnabled, bool, false);
+  DECL_MEDIA_PREF("media.suspend-bkgnd-video.delay-ms",       MDSMSuspendBackgroundVideoDelay, uint32_t, SUSPEND_BACKGROUND_VIDEO_DELAY_MS);
+
   // WebSpeech
   DECL_MEDIA_PREF("media.webspeech.synth.force_global_queue", WebSpeechForceGlobal, bool, false);
   DECL_MEDIA_PREF("media.webspeech.test.enable",              WebSpeechTestEnabled, bool, false);
   DECL_MEDIA_PREF("media.webspeech.test.fake_fsm_events",     WebSpeechFakeFSMEvents, bool, false);
   DECL_MEDIA_PREF(TEST_PREFERENCE_FAKE_RECOGNITION_SERVICE,   WebSpeechFakeRecognitionService, bool, false);
   DECL_MEDIA_PREF("media.webspeech.recognition.enable",       WebSpeechRecognitionEnabled, bool, false);
   DECL_MEDIA_PREF("media.webspeech.recognition.force_enable", WebSpeechRecognitionForceEnabled, bool, false);
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -375,16 +375,19 @@ pref("media.decoder-doctor.notifications
 pref("media.decoder-doctor.verbose", false);
 
 // Whether to suspend decoding of videos in background tabs.
 #ifdef NIGHTLY_BUILD
 pref("media.suspend-bkgnd-video.enabled", true);
 #else
 pref("media.suspend-bkgnd-video.enabled", false);
 #endif
+// Delay, in ms, from time window goes to background to suspending
+// video decoders. Defaults to 10 seconds.
+pref("media.suspend-bkgnd-video.delay-ms", 10000);
 
 #ifdef MOZ_WEBRTC
 pref("media.navigator.enabled", true);
 pref("media.navigator.video.enabled", true);
 pref("media.navigator.load_adapt", true);
 pref("media.navigator.load_adapt.measure_interval",1000);
 pref("media.navigator.load_adapt.avg_seconds",3);
 pref("media.navigator.load_adapt.high_load","0.90");