Bug 996465 - extract code running state mahcine cycles into a class. r=cpearce.
☠☠ backed out by b9457b4aaf38 ☠ ☠
authorJW Wang <jwwang@mozilla.com>
Thu, 10 Jul 2014 03:21:00 +0200
changeset 215478 bbcfcf00f84e20c33d3680d61ead28c2a385c689
parent 215477 cf85693280c77de186b5d1ce194615d91e6f430e
child 215479 aa529bac2a92d02a8282691434bd65d81bc5707e
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs996465
milestone33.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 996465 - extract code running state mahcine cycles into a class. r=cpearce.
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/MediaDecoderStateMachineScheduler.cpp
content/media/MediaDecoderStateMachineScheduler.h
content/media/moz.build
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -8,16 +8,17 @@
 #include "windows.h"
 #include "mmsystem.h"
 #endif
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h>
 
 #include "MediaDecoderStateMachine.h"
+#include "MediaDecoderStateMachineScheduler.h"
 #include "AudioSink.h"
 #include "nsTArray.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "nsDeque.h"
@@ -163,18 +164,21 @@ static TimeDuration UsecsToDuration(int6
 static int64_t DurationToUsecs(TimeDuration aDuration) {
   return static_cast<int64_t>(aDuration.ToSeconds() * USECS_PER_S);
 }
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
+  mScheduler(new MediaDecoderStateMachineScheduler(
+      aDecoder->GetReentrantMonitor(),
+      &MediaDecoderStateMachine::TimeoutExpired,
+      MOZ_THIS_IN_INITIALIZER_LIST(), aRealTime)),
   mState(DECODER_STATE_DECODING_METADATA),
-  mInRunningStateMachine(false),
   mSyncPointInMediaStream(-1),
   mSyncPointInDecodedStream(-1),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
@@ -193,40 +197,34 @@ MediaDecoderStateMachine::MediaDecoderSt
   mPositionChangeQueued(false),
   mAudioCompleted(false),
   mGotDurationFromMetaData(false),
   mDispatchedEventToDecode(false),
   mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
-  mRealTime(aRealTime),
   mDispatchedDecodeMetadataTask(false),
   mDropAudioUntilNextDiscontinuity(false),
   mDropVideoUntilNextDiscontinuity(false),
   mDecodeToSeekTarget(false),
   mCurrentTimeBeforeSeek(0),
-  mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED),
-  mTimerId(0)
+  mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
-  // Only enable realtime mode when "media.realtime_decoder.enabled" is true.
-  if (Preferences::GetBool("media.realtime_decoder.enabled", false) == false)
-    mRealTime = false;
-
   mAmpleVideoFrames =
     std::max<uint32_t>(Preferences::GetUint("media.video-queue.default-size", 10), 3);
 
-  mBufferingWait = mRealTime ? 0 : BUFFERING_WAIT_S;
-  mLowDataThresholdUsecs = mRealTime ? 0 : LOW_DATA_THRESHOLD_USECS;
-
-  mVideoPrerollFrames = mRealTime ? 0 : mAmpleVideoFrames / 2;
-  mAudioPrerollUsecs = mRealTime ? 0 : LOW_AUDIO_USECS * 2;
+  mBufferingWait = mScheduler->IsRealTime() ? 0 : BUFFERING_WAIT_S;
+  mLowDataThresholdUsecs = mScheduler->IsRealTime() ? 0 : LOW_DATA_THRESHOLD_USECS;
+
+  mVideoPrerollFrames = mScheduler->IsRealTime() ? 0 : mAmpleVideoFrames / 2;
+  mAudioPrerollUsecs = mScheduler->IsRealTime() ? 0 : LOW_AUDIO_USECS * 2;
 
 #ifdef XP_WIN
   // Ensure high precision timers are enabled on Windows, otherwise the state
   // machine thread isn't woken up at reliable intervals to set the next frame,
   // and we drop frames while painting. Note that multiple calls to this
   // function per-process is OK, provided each call is matched by a corresponding
   // timeEndPeriod() call.
   timeBeginPeriod(1);
@@ -236,18 +234,16 @@ MediaDecoderStateMachine::MediaDecoderSt
 MediaDecoderStateMachine::~MediaDecoderStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(MediaDecoderStateMachine);
   NS_ASSERTION(!mPendingWakeDecoder.get(),
                "WakeDecoder should have been revoked already");
 
   MOZ_ASSERT(!mDecodeTaskQueue, "Should be released in SHUTDOWN");
-  // No need to cancel the timer here for we've done that in SHUTDOWN.
-  MOZ_ASSERT(!mTimer, "Should be released in SHUTDOWN");
   mReader = nullptr;
 
 #ifdef XP_WIN
   timeEndPeriod(1);
 #endif
 }
 
 bool MediaDecoderStateMachine::HasFutureAudio() {
@@ -1058,34 +1054,25 @@ bool MediaDecoderStateMachine::IsPlaying
 
 nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<SharedThreadPool> decodePool(GetMediaDecodeThreadPool());
   NS_ENSURE_TRUE(decodePool, NS_ERROR_FAILURE);
 
-  RefPtr<SharedThreadPool> stateMachinePool(
-    SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1));
-  NS_ENSURE_TRUE(stateMachinePool, NS_ERROR_FAILURE);
-
   mDecodeTaskQueue = new MediaTaskQueue(decodePool.forget());
   NS_ENSURE_TRUE(mDecodeTaskQueue, NS_ERROR_FAILURE);
 
   MediaDecoderReader* cloneReader = nullptr;
   if (aCloneDonor) {
     cloneReader = aCloneDonor->mReader;
   }
 
-  mStateMachineThreadPool = stateMachinePool;
-
-  nsresult rv;
-  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mTimer->SetTarget(GetStateMachineThread());
+  nsresult rv = mScheduler->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Note: This creates a cycle, broken in shutdown.
   mMediaDecodedListener =
     new MediaDataDecodedListener<MediaDecoderStateMachine>(this,
                                                            mDecodeTaskQueue);
   mReader->SetCallback(mMediaDecodedListener);
   mReader->SetTaskQueue(mDecodeTaskQueue);
@@ -1337,18 +1324,18 @@ void MediaDecoderStateMachine::Shutdown(
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Once we've entered the shutdown state here there's no going back.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Change state before issuing shutdown request to threads so those
   // threads can start exiting cleanly during the Shutdown call.
   DECODER_LOG(PR_LOG_DEBUG, "Changed state to SHUTDOWN");
-  ScheduleStateMachine();
   mState = DECODER_STATE_SHUTDOWN;
+  mScheduler->ScheduleAndShutdown();
   if (mAudioSink) {
     mAudioSink->PrepareToShutdown();
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
@@ -1748,16 +1735,17 @@ MediaDecoderStateMachine::StartAudioThre
   if (HasAudio() && !mAudioSink) {
     mAudioCompleted = false;
     mAudioSink = new AudioSink(this,
                                mAudioStartTime, mInfo.mAudio, mDecoder->GetAudioChannel());
     nsresult rv = mAudioSink->Init();
     if (NS_FAILED(rv)) {
       DECODER_LOG(PR_LOG_WARNING, "Changed state to SHUTDOWN because audio sink initialization failed");
       mState = DECODER_STATE_SHUTDOWN;
+      mScheduler->ScheduleAndShutdown();
       return rv;
     }
 
     mAudioSink->SetVolume(mVolume);
     mAudioSink->SetPlaybackRate(mPlaybackRate);
     mAudioSink->SetPreservesPitch(mPreservesPitch);
   }
   return NS_OK;
@@ -1825,18 +1813,18 @@ MediaDecoderStateMachine::DecodeError()
     // Already shutdown.
     return;
   }
 
   // Change state to shutdown before sending error report to MediaDecoder
   // and the HTMLMediaElement, so that our pipeline can start exiting
   // cleanly during the sync dispatch below.
   DECODER_LOG(PR_LOG_WARNING, "Decode error, changed state to SHUTDOWN due to error");
-  ScheduleStateMachine();
   mState = DECODER_STATE_SHUTDOWN;
+  mScheduler->ScheduleAndShutdown();
   mDecoder->GetReentrantMonitor().NotifyAll();
 
   // Dispatch the event to call DecodeError synchronously. This ensures
   // we're in shutdown state by the time we exit the decode thread.
   // If we just moved to shutdown state here on the decode thread, we may
   // cause the state machine to shutdown/free memory without closing its
   // media stream properly, and we'll get callbacks from the media stream
   // causing a crash.
@@ -1906,17 +1894,17 @@ nsresult MediaDecoderStateMachine::Decod
     AudioQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
   }
   if (HasVideo()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
     VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
   }
 
-  if (mRealTime) {
+  if (mScheduler->IsRealTime()) {
     SetStartTime(0);
     res = FinishDecodeMetadata();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
       mReader->RequestAudioData();
     }
@@ -1935,17 +1923,17 @@ MediaDecoderStateMachine::FinishDecodeMe
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers");
 
   if (mState == DECODER_STATE_SHUTDOWN) {
     return NS_ERROR_FAILURE;
   }
 
-  if (!mRealTime) {
+  if (!mScheduler->IsRealTime()) {
 
     const VideoData* v = VideoQueue().PeekFront();
     const AudioData* a = AudioQueue().PeekFront();
 
     int64_t startTime = std::min<int64_t>(a ? a->mTime : INT64_MAX,
                                           v ? v->mTime : INT64_MAX);
     if (startTime == INT64_MAX) {
       startTime = 0;
@@ -2287,18 +2275,16 @@ nsresult MediaDecoderStateMachine::RunSt
       // state machine) needs to finish and be released in order to allow
       // that. So we dispatch an event to run after this event runner has
       // finished and released its monitor/references. That event then will
       // dispatch an event to the main thread to release the decoder and
       // state machine.
       GetStateMachineThread()->Dispatch(
         new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
 
-      mTimer->Cancel();
-      mTimer = nullptr;
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
       if (IsPlaying()) {
         StopPlayback();
       }
       FlushDecoding();
@@ -2584,17 +2570,17 @@ void MediaDecoderStateMachine::AdvanceFr
   int64_t remainingTime = AUDIO_DURATION_USECS;
   NS_ASSERTION(clock_time >= mStartTime, "Should have positive clock time.");
   nsAutoPtr<VideoData> currentFrame;
 #ifdef PR_LOGGING
   int32_t droppedFrames = 0;
 #endif
   if (VideoQueue().GetSize() > 0) {
     VideoData* frame = VideoQueue().PeekFront();
-    while (mRealTime || clock_time >= frame->mTime) {
+    while (mScheduler->IsRealTime() || clock_time >= frame->mTime) {
       mVideoFrameEndTime = frame->GetEndTime();
       currentFrame = frame;
 #ifdef PR_LOGGING
       VERBOSE_LOG("discarding video frame %lld", frame->mTime);
       if (droppedFrames++) {
         VERBOSE_LOG("discarding video frame %lld (%d so far)", frame->mTime, droppedFrames-1);
       }
 #endif
@@ -2924,128 +2910,53 @@ nsresult MediaDecoderStateMachine::CallR
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
 
   // If audio is being captured, stop the audio sink if it's running
   if (mAudioCaptured) {
     StopAudioThread();
   }
 
-  MOZ_ASSERT(!mInRunningStateMachine, "State machine cycles must run in sequence!");
-  mTimeout = TimeStamp();
-  mInRunningStateMachine = true;
-  nsresult res = RunStateMachine();
-  mInRunningStateMachine = false;
-  return res;
+  return RunStateMachine();
 }
 
-nsresult MediaDecoderStateMachine::TimeoutExpired(int aTimerId)
+nsresult MediaDecoderStateMachine::TimeoutExpired(void* aClosure)
 {
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  NS_ASSERTION(OnStateMachineThread(), "Must be on state machine thread");
-  mTimer->Cancel();
-  if (mTimerId == aTimerId) {
-    return CallRunStateMachine();
-  } else {
-    return NS_OK;
-  }
+  MediaDecoderStateMachine* p = static_cast<MediaDecoderStateMachine*>(aClosure);
+  return p->CallRunStateMachine();
 }
 
 void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DispatchAudioDecodeTaskIfNeeded();
   DispatchVideoDecodeTaskIfNeeded();
 }
 
-class TimerEvent : public nsITimerCallback, public nsRunnable {
-  NS_DECL_THREADSAFE_ISUPPORTS
-public:
-  TimerEvent(MediaDecoderStateMachine* aStateMachine, int aTimerId)
-    : mStateMachine(aStateMachine), mTimerId(aTimerId) {}
-
-  NS_IMETHOD Run() MOZ_OVERRIDE {
-    return mStateMachine->TimeoutExpired(mTimerId);
-  }
-
-  NS_IMETHOD Notify(nsITimer* aTimer) {
-    return mStateMachine->TimeoutExpired(mTimerId);
-  }
-private:
-  ~TimerEvent() {}
-
-  const nsRefPtr<MediaDecoderStateMachine> mStateMachine;
-  int mTimerId;
-};
-
-NS_IMPL_ISUPPORTS(TimerEvent, nsITimerCallback, nsIRunnable);
-
 nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
-  AssertCurrentThreadInMonitor();
-  NS_ABORT_IF_FALSE(GetStateMachineThread(),
-    "Must have a state machine thread to schedule");
-
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    return NS_ERROR_FAILURE;
-  }
-  aUsecs = std::max<int64_t>(aUsecs, 0);
-
-  TimeStamp timeout = TimeStamp::Now() + UsecsToDuration(aUsecs);
-  if (!mTimeout.IsNull() && timeout >= mTimeout) {
-    // We've already scheduled a timer set to expire at or before this time,
-    // or have an event dispatched to run the state machine.
-    return NS_OK;
-  }
-
-  uint32_t ms = static_cast<uint32_t>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
-  if (mRealTime && ms > 40) {
-    ms = 40;
-  }
-
-  // Don't cancel the timer here for this function will be called from
-  // different threads.
-
-  nsresult rv = NS_ERROR_FAILURE;
-  nsRefPtr<TimerEvent> event = new TimerEvent(this, mTimerId+1);
-
-  if (ms == 0) {
-    // Dispatch a runnable to the state machine thread when delay is 0.
-    // It will has less latency than dispatching a runnable to the state
-    // machine thread which will then schedule a zero-delay timer.
-    rv = GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
-  } else if (OnStateMachineThread()) {
-    rv = mTimer->InitWithCallback(event, ms, nsITimer::TYPE_ONE_SHOT);
-  } else {
-    MOZ_ASSERT(false, "non-zero delay timer should be only scheduled in state machine thread");
-  }
-
-  if (NS_SUCCEEDED(rv)) {
-    mTimeout = timeout;
-    ++mTimerId;
-  } else {
-    NS_WARNING("Failed to schedule state machine");
-  }
-
-  return rv;
+  return mScheduler->Schedule(aUsecs);
 }
 
 bool MediaDecoderStateMachine::OnDecodeThread() const
 {
   return mDecodeTaskQueue->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::OnStateMachineThread() const
 {
-  bool rv = false;
-  mStateMachineThreadPool->IsOnCurrentThread(&rv);
-  return rv;
+  return mScheduler->OnStateMachineThread();
 }
 
-nsIEventTarget* MediaDecoderStateMachine::GetStateMachineThread()
+nsIEventTarget* MediaDecoderStateMachine::GetStateMachineThread() const
 {
-  return mStateMachineThreadPool->GetEventTarget();
+  return mScheduler->GetStateMachineThread();
+}
+
+bool MediaDecoderStateMachine::IsStateMachineScheduled() const
+{
+  return mScheduler->IsScheduled();
 }
 
 void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(aPlaybackRate != 0,
       "PlaybackRate == 0 should be handled before this function.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -95,16 +95,17 @@ class nsITimer;
 
 namespace mozilla {
 
 class AudioSegment;
 class VideoSegment;
 class MediaTaskQueue;
 class SharedThreadPool;
 class AudioSink;
+class MediaDecoderStateMachineScheduler;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
 // implementation.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
@@ -274,29 +275,30 @@ public:
   void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
   int64_t GetEndMediaTime() const {
     AssertCurrentThreadInMonitor();
     return mEndTime;
   }
 
   // Returns the shared state machine thread.
-  nsIEventTarget* GetStateMachineThread();
+  nsIEventTarget* GetStateMachineThread() const;
 
   // Calls ScheduleStateMachine() after taking the decoder lock. Also
   // notifies the decoder thread in case it's waiting on the decoder lock.
   void ScheduleStateMachineWithLockAndWakeDecoder();
 
   // Schedules the shared state machine thread to run the state machine
   // in aUsecs microseconds from now, if it's not already scheduled to run
   // earlier, in which case the request is discarded.
   nsresult ScheduleStateMachine(int64_t aUsecs = 0);
 
-  // Timer function to implement ScheduleStateMachine(aUsecs).
-  nsresult TimeoutExpired(int aGeneration);
+  // Callback function registered with MediaDecoderStateMachineScheduler
+  // to run state machine cycles.
+  static nsresult TimeoutExpired(void* aClosure);
 
   // Set the media fragment end time. aEndTime is in microseconds.
   void SetFragmentEndTime(int64_t aEndTime);
 
   // Drop reference to decoder.  Only called during shutdown dance.
   void BreakCycles() {
     if (mReader) {
       mReader->BreakCycles();
@@ -442,17 +444,17 @@ protected:
   // Dispatches an asynchronous event to update the media element's ready state.
   void UpdateReadyState();
 
   // Resets playback timing data. Called when we seek, on the decode thread.
   void ResetPlayback();
 
   // Orders the Reader to stop decoding, and blocks until the Reader
   // has stopped decoding and finished delivering samples, then calls
-  // ResetPlayback() to discard all enqueued data. 
+  // ResetPlayback() to discard all enqueued data.
   void FlushDecoding();
 
   // Returns the audio clock, if we have audio, or -1 if we don't.
   // Called on the state machine thread.
   int64_t GetAudioClock();
 
   // Get the video stream position, taking the |playbackRate| change into
   // account. This is a position in the media, not the duration of the playback
@@ -605,20 +607,17 @@ protected:
   // State machine thread run function. Defers to RunStateMachine().
   nsresult CallRunStateMachine();
 
   // Performs one "cycle" of the state machine. Polls the state, and may send
   // a video frame to be displayed, and generally manages the decode. Called
   // periodically via timer to ensure the video stays in sync.
   nsresult RunStateMachine();
 
-  bool IsStateMachineScheduled() const {
-    AssertCurrentThreadInMonitor();
-    return !mTimeout.IsNull();
-  }
+  bool IsStateMachineScheduled() const;
 
   // Returns true if we're not playing and the decode thread has filled its
   // decode buffers and is waiting. We can shut the decode thread down in this
   // case as it may not be needed again.
   bool IsPausedAndDecoderWaiting();
 
   // These return true if the respective stream's decode has not yet reached
   // the end of stream.
@@ -645,16 +644,20 @@ protected:
   // dropped its reference to the decoder. This enables the state machine to
   // keep using the decoder's monitor until the state machine has finished
   // shutting down, without fear of the monitor being destroyed. After
   // shutting down, the state machine will then release this reference,
   // causing the decoder to be destroyed. This is accessed on the decode,
   // state machine, audio and main threads.
   nsRefPtr<MediaDecoder> mDecoder;
 
+  // Used to schedule state machine cycles. This should never outlive
+  // the life cycle of the state machine.
+  const nsAutoPtr<MediaDecoderStateMachineScheduler> mScheduler;
+
   // Time at which the last video sample was requested. If it takes too long
   // before the sample arrives, we will increase the amount of audio we buffer.
   // This is necessary for legacy synchronous decoders to prevent underruns.
   TimeStamp mVideoDecodeStartTime;
 
   // Queue of audio frames. This queue is threadsafe, and is accessed from
   // the audio, decoder, state machine, and main threads.
   MediaQueue<AudioData> mAudioQueue;
@@ -669,29 +672,16 @@ protected:
   // Accessed on state machine, audio, main, and AV thread.
   State mState;
 
   // The task queue in which we run decode tasks. This is referred to as
   // the "decode thread", though in practise tasks can run on a different
   // thread every time they're called.
   RefPtr<MediaTaskQueue> mDecodeTaskQueue;
 
-  RefPtr<SharedThreadPool> mStateMachineThreadPool;
-
-  // Timer to run the state machine cycles. Used by
-  // ScheduleStateMachine(). Access protected by decoder monitor.
-  nsCOMPtr<nsITimer> mTimer;
-
-  // Timestamp at which the next state machine cycle will run.
-  // Access protected by decoder monitor.
-  TimeStamp mTimeout;
-
-  // Used to check if there are state machine cycles are running in sequence.
-  DebugOnly<bool> mInRunningStateMachine;
-
   // The time that playback started from the system clock. This is used for
   // timing the presentation of video frames when there's no audio.
   // Accessed only via the state machine thread.  Must be set via SetPlayStartTime.
   TimeStamp mPlayStartTime;
 
   // When we start writing decoded data to a new DecodedDataStream, or we
   // restart writing due to PlaybackStarted(), we record where we are in the
   // MediaStream and what that corresponds to in the media.
@@ -906,19 +896,16 @@ protected:
   // memory and CPU overhead.
   bool mMinimizePreroll;
 
   // True if the decode thread has gone filled its buffers and is now
   // waiting to be awakened before it continues decoding. Synchronized
   // by the decoder monitor.
   bool mDecodeThreadWaiting;
 
-  // True is we are decoding a realtime stream, like a camera stream
-  bool mRealTime;
-
   // True if we've dispatched a task to the decode task queue to call
   // ReadMetadata on the reader. We maintain a flag to ensure that we don't
   // dispatch multiple tasks to re-do the metadata loading.
   bool mDispatchedDecodeMetadataTask;
 
   // These two flags are true when we need to drop decoded samples that
   // we receive up to the next discontinuity. We do this when we seek;
   // the first sample in each stream after the seek is marked as being
@@ -937,15 +924,12 @@ protected:
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
 
   mozilla::MediaMetadataManager mMetadataManager;
 
   MediaDecoderOwner::NextFrameStatus mLastFrameStatus;
-
-  // The id of timer tasks, used to ignore tasks that are scheduled previously.
-  int mTimerId;
 };
 
 } // namespace mozilla;
 #endif
new file mode 100644
--- /dev/null
+++ b/content/media/MediaDecoderStateMachineScheduler.cpp
@@ -0,0 +1,197 @@
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDecoderStateMachineScheduler.h"
+#include "SharedThreadPool.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+#include "VideoUtils.h"
+
+static mozilla::TimeDuration UsecsToDuration(int64_t aUsecs) {
+  return mozilla::TimeDuration::FromMilliseconds(
+      static_cast<double>(aUsecs) / mozilla::USECS_PER_MS);
+}
+
+namespace {
+class TimerEvent : public nsITimerCallback, public nsRunnable {
+  typedef mozilla::MediaDecoderStateMachineScheduler Scheduler;
+  NS_DECL_THREADSAFE_ISUPPORTS
+public:
+  TimerEvent(Scheduler* aScheduler, int aTimerId)
+    : mScheduler(aScheduler), mTimerId(aTimerId) {}
+
+  NS_IMETHOD Run() MOZ_OVERRIDE {
+    return mScheduler->TimeoutExpired(mTimerId);
+  }
+
+  NS_IMETHOD Notify(nsITimer* aTimer) MOZ_OVERRIDE {
+    return mScheduler->TimeoutExpired(mTimerId);
+  }
+private:
+  ~TimerEvent() {}
+  Scheduler* const mScheduler;
+  const int mTimerId;
+};
+
+NS_IMPL_ISUPPORTS(TimerEvent, nsITimerCallback, nsIRunnable);
+} // anonymous namespace
+
+static already_AddRefed<nsIEventTarget>
+CreateStateMachineThread()
+{
+  using mozilla::SharedThreadPool;
+  using mozilla::RefPtr;
+  RefPtr<SharedThreadPool> threadPool(
+      SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1));
+  nsCOMPtr<nsIEventTarget> rv = threadPool.get();
+  return rv.forget();
+}
+
+namespace mozilla {
+
+MediaDecoderStateMachineScheduler::MediaDecoderStateMachineScheduler(
+    ReentrantMonitor& aMonitor,
+    nsresult (*aTimeoutCallback)(void*),
+    void* aClosure, bool aRealTime)
+  : mTimeoutCallback(aTimeoutCallback)
+  , mClosure(aClosure)
+  // Only enable realtime mode when "media.realtime_decoder.enabled" is true.
+  , mRealTime(aRealTime &&
+              Preferences::GetBool("media.realtime_decoder.enabled", false))
+  , mMonitor(aMonitor)
+  , mEventTarget(CreateStateMachineThread())
+  , mTimer(do_CreateInstance("@mozilla.org/timer;1"))
+  , mTimerId(0)
+  , mState(SCHEDULER_STATE_NONE)
+  , mInRunningStateMachine(false)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_CTOR(MediaDecoderStateMachineScheduler);
+}
+
+MediaDecoderStateMachineScheduler::~MediaDecoderStateMachineScheduler()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_DTOR(MediaDecoderStateMachineScheduler);
+}
+
+nsresult
+MediaDecoderStateMachineScheduler::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mEventTarget, NS_ERROR_FAILURE);
+  nsresult rv = mTimer->SetTarget(mEventTarget);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+nsresult
+MediaDecoderStateMachineScheduler::Schedule(int64_t aUsecs)
+{
+  mMonitor.AssertCurrentThreadIn();
+
+  if (mState == SCHEDULER_STATE_SHUTDOWN) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aUsecs = std::max<int64_t>(aUsecs, 0);
+
+  TimeStamp timeout = TimeStamp::Now() + UsecsToDuration(aUsecs);
+  if (!mTimeout.IsNull() && timeout >= mTimeout) {
+    // We've already scheduled a timer set to expire at or before this time,
+    // or have an event dispatched to run the state machine.
+    return NS_OK;
+  }
+
+  uint32_t ms = static_cast<uint32_t>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
+  if (IsRealTime() && ms > 40) {
+    ms = 40;
+  }
+
+  // Don't cancel the timer here for this function will be called from
+  // different threads.
+
+  nsresult rv = NS_ERROR_FAILURE;
+  nsRefPtr<TimerEvent> event = new TimerEvent(this, mTimerId+1);
+
+  if (ms == 0) {
+    // Dispatch a runnable to the state machine thread when delay is 0.
+    // It will has less latency than dispatching a runnable to the state
+    // machine thread which will then schedule a zero-delay timer.
+    rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+  } else if (OnStateMachineThread()) {
+    rv = mTimer->InitWithCallback(event, ms, nsITimer::TYPE_ONE_SHOT);
+  } else {
+    MOZ_ASSERT(false, "non-zero delay timer should be only "
+                      "scheduled in state machine thread");
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    mTimeout = timeout;
+    ++mTimerId;
+  } else {
+    NS_WARNING("Failed to schedule state machine");
+  }
+
+  return rv;
+}
+
+nsresult
+MediaDecoderStateMachineScheduler::TimeoutExpired(int aTimerId)
+{
+  ReentrantMonitorAutoEnter mon(mMonitor);
+  MOZ_ASSERT(OnStateMachineThread());
+  MOZ_ASSERT(!mInRunningStateMachine,
+             "State machine cycles must run in sequence!");
+
+  mInRunningStateMachine = true;
+  // Only run state machine cycles when id matches.
+  nsresult rv = NS_OK;
+  if (mTimerId == aTimerId) {
+    ResetTimer();
+    rv = mTimeoutCallback(mClosure);
+  }
+  mInRunningStateMachine = false;
+
+  return rv;
+}
+
+void
+MediaDecoderStateMachineScheduler::ScheduleAndShutdown()
+{
+  mMonitor.AssertCurrentThreadIn();
+  // Schedule next cycle to handle SHUTDOWN in state machine thread.
+  Schedule();
+  // This must be set after calling Schedule()
+  // which does nothing in shutdown state.
+  mState = SCHEDULER_STATE_SHUTDOWN;
+}
+
+bool
+MediaDecoderStateMachineScheduler::OnStateMachineThread() const
+{
+  bool rv = false;
+  mEventTarget->IsOnCurrentThread(&rv);
+  return rv;
+}
+
+bool
+MediaDecoderStateMachineScheduler::IsScheduled() const
+{
+  mMonitor.AssertCurrentThreadIn();
+  return !mTimeout.IsNull();
+}
+
+void
+MediaDecoderStateMachineScheduler::ResetTimer()
+{
+  mMonitor.AssertCurrentThreadIn();
+  mTimer->Cancel();
+  mTimeout = TimeStamp();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/MediaDecoderStateMachineScheduler.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaDecoderStateMachineScheduler_h__
+#define MediaDecoderStateMachineScheduler_h__
+
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/DebugOnly.h"
+
+class nsITimer;
+class nsIEventTarget;
+
+namespace mozilla {
+
+class ReentrantMonitor;
+
+class MediaDecoderStateMachineScheduler {
+  enum State {
+    SCHEDULER_STATE_NONE,
+    SCHEDULER_STATE_SHUTDOWN
+  };
+public:
+  MediaDecoderStateMachineScheduler(ReentrantMonitor& aMonitor,
+                                    nsresult (*aTimeoutCallback)(void*),
+                                    void* aClosure, bool aRealTime);
+  ~MediaDecoderStateMachineScheduler();
+  nsresult Init();
+  nsresult Schedule(int64_t aUsecs = 0);
+  void ScheduleAndShutdown();
+  nsresult TimeoutExpired(int aTimerId);
+
+  bool OnStateMachineThread() const;
+  bool IsScheduled() const;
+
+  bool IsRealTime() const {
+    return mRealTime;
+  }
+
+  nsIEventTarget* GetStateMachineThread() const {
+    return mEventTarget;
+  }
+
+private:
+  void ResetTimer();
+
+  // Callback function provided by MediaDecoderStateMachine to run
+  // state machine cycles.
+  nsresult (*const mTimeoutCallback)(void*);
+  // Since StateMachineScheduler will never outlive the state machine,
+  // it is safe to keep a raw pointer only to avoid reference cycles.
+  void* const mClosure;
+  // True is we are decoding a realtime stream, like a camera stream
+  const bool mRealTime;
+  // Monitor of the decoder
+  ReentrantMonitor& mMonitor;
+  // State machine thread
+  const nsCOMPtr<nsIEventTarget> mEventTarget;
+  // Timer to schedule callbacks to run the state machine cycles.
+  nsCOMPtr<nsITimer> mTimer;
+  // Timestamp at which the next state machine cycle will run.
+  TimeStamp mTimeout;
+  // The id of timer tasks, timer callback will only run if id matches.
+  int mTimerId;
+  // No more state machine cycles in shutdown state.
+  State mState;
+
+  // Used to check if state machine cycles are running in sequence.
+  DebugOnly<bool> mInRunningStateMachine;
+};
+
+} // namespace mozilla
+
+#endif // MediaDecoderStateMachineScheduler_h__
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -139,16 +139,17 @@ UNIFIED_SOURCES += [
     'DOMMediaStream.cpp',
     'EncodedBufferCache.cpp',
     'FileBlockCache.cpp',
     'MediaCache.cpp',
     'MediaData.cpp',
     'MediaDecoder.cpp',
     'MediaDecoderReader.cpp',
     'MediaDecoderStateMachine.cpp',
+    'MediaDecoderStateMachineScheduler.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamTrack.cpp',
     'MediaTaskQueue.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',