Back out 3 changesets (bug 1135424) on suspicion of causing frequent hangs in test_playback.html on mochitest-e10s
authorPhil Ringnalda <philringnalda@gmail.com>
Thu, 12 Mar 2015 23:05:11 -0700
changeset 262326 1e99f9913951b8a4b1b4540443e3a24769b236b7
parent 262325 dd04d7c32b673a0af2455ec2437d8c9ab9850167
child 262327 93dac82da4edb03585d7748ad25e44caa41a2136
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1135424
milestone39.0a1
backs out584d91ffdf884ffa169f9d290acc8b4aff312ade
d86806ea63f405368cdd9e5dc071e2ceb9c74df5
e52401d30a67e97c77ab891565446053b0b01533
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
Back out 3 changesets (bug 1135424) on suspicion of causing frequent hangs in test_playback.html on mochitest-e10s CLOSED TREE Backed out changeset 584d91ffdf88 (bug 1135424) Backed out changeset d86806ea63f4 (bug 1135424) Backed out changeset e52401d30a67 (bug 1135424)
dom/media/MediaDecoder.cpp
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaDecoderStateMachineScheduler.cpp
dom/media/MediaDecoderStateMachineScheduler.h
dom/media/MediaPromise.cpp
dom/media/MediaPromise.h
dom/media/MediaQueue.h
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
dom/media/MediaTimer.cpp
dom/media/MediaTimer.h
dom/media/moz.build
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -186,38 +186,38 @@ void MediaDecoder::UpdateDormantState(bo
   if (prevDormant == mIsDormant) {
     // No update to dormant state
     return;
   }
 
   if (mIsDormant) {
     DECODER_LOG("UpdateDormantState() entering DORMANT state");
     // enter dormant state
-    RefPtr<nsRunnable> event =
+    nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethodWithArg<bool>(
         mDecoderStateMachine,
         &MediaDecoderStateMachine::SetDormant,
         true);
-    mDecoderStateMachine->TaskQueue()->Dispatch(event);
+    mDecoderStateMachine->GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
 
     if (IsEnded()) {
       mWasEndedWhenEnteredDormant = true;
     }
     mNextState = mPlayState;
     ChangeState(PLAY_STATE_LOADING);
   } else {
     DECODER_LOG("UpdateDormantState() leaving DORMANT state");
     // exit dormant state
     // trigger to state machine.
-    RefPtr<nsRunnable> event =
+    nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethodWithArg<bool>(
         mDecoderStateMachine,
         &MediaDecoderStateMachine::SetDormant,
         false);
-    mDecoderStateMachine->TaskQueue()->Dispatch(event);
+    mDecoderStateMachine->GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
   }
 }
 
 void MediaDecoder::DormantTimerExpired(nsITimer* aTimer, void* aClosure)
 {
   MOZ_ASSERT(aClosure);
   MediaDecoder* decoder = static_cast<MediaDecoder*>(aClosure);
   ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
@@ -745,31 +745,31 @@ nsresult MediaDecoder::ScheduleStateMach
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(mDecoderStateMachine,
                "Must have state machine to start state machine thread");
   NS_ENSURE_STATE(mDecoderStateMachine);
 
   if (mShuttingDown)
     return NS_OK;
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  mDecoderStateMachine->ScheduleStateMachine();
-  return NS_OK;
+  return mDecoderStateMachine->ScheduleStateMachine();
 }
 
 nsresult MediaDecoder::Play()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
 
   NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
   if (mPausedForPlaybackRateNull) {
     return NS_OK;
   }
-  ScheduleStateMachineThread();
+  nsresult res = ScheduleStateMachineThread();
+  NS_ENSURE_SUCCESS(res,res);
   if (IsEnded()) {
     return Seek(0, SeekTarget::PrevSyncPoint);
   } else if (mPlayState == PLAY_STATE_LOADING || mPlayState == PLAY_STATE_SEEKING) {
     mNextState = PLAY_STATE_PLAYING;
     return NS_OK;
   }
 
   ChangeState(PLAY_STATE_PLAYING);
@@ -1312,17 +1312,17 @@ void MediaDecoder::ApplyStateToStateMach
   GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (mDecoderStateMachine) {
     switch (aState) {
       case PLAY_STATE_PLAYING:
         mDecoderStateMachine->Play();
         break;
       case PLAY_STATE_SEEKING:
-        mSeekRequest.Begin(ProxyMediaCall(mDecoderStateMachine->TaskQueue(),
+        mSeekRequest.Begin(ProxyMediaCall(mDecoderStateMachine->GetStateMachineThread(),
                                           mDecoderStateMachine.get(), __func__,
                                           &MediaDecoderStateMachine::Seek, mRequestedSeekTarget)
           ->RefableThen(NS_GetCurrentThread(), __func__, this,
                         &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
         mRequestedSeekTarget.Reset();
         break;
       default:
         // The state machine checks for things like PAUSED in RunStateMachine.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -8,17 +8,17 @@
 #include "windows.h"
 #include "mmsystem.h"
 #endif
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h>
 
 #include "MediaDecoderStateMachine.h"
-#include "MediaTimer.h"
+#include "MediaDecoderStateMachineScheduler.h"
 #include "AudioSink.h"
 #include "nsTArray.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/TimeRanges.h"
@@ -196,19 +196,19 @@ static const uint32_t MAX_VIDEO_QUEUE_SI
 
 static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
 static uint32_t sVideoQueueHWAccelSize = MIN_VIDEO_QUEUE_SIZE;
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
-  mRealTime(aRealTime),
-  mDispatchedStateMachine(false),
-  mDelayedScheduler(this),
+  mScheduler(new MediaDecoderStateMachineScheduler(
+      aDecoder->GetReentrantMonitor(),
+      &MediaDecoderStateMachine::TimeoutExpired, this, aRealTime)),
   mState(DECODER_STATE_DECODING_NONE),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mDurationSet(false),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
@@ -243,21 +243,16 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDisabledHardwareAcceleration(false),
   mDecodingFrozenAtStateDecoding(false),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
-  // Set up our task queue.
-  RefPtr<SharedThreadPool> threadPool(
-      SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1));
-  mTaskQueue = new MediaTaskQueue(threadPool.forget());
-
   static bool sPrefCacheInit = false;
   if (!sPrefCacheInit) {
     sPrefCacheInit = true;
     Preferences::AddUintVarCache(&sVideoQueueDefaultSize,
                                  "media.video-queue.default-size",
                                  MAX_VIDEO_QUEUE_SIZE);
     Preferences::AddUintVarCache(&sVideoQueueHWAccelSize,
                                  "media.video-queue.hw-accel-size",
@@ -387,17 +382,17 @@ static void WriteVideoToMediaStream(Medi
       aStream->MicrosecondsToStreamTimeRoundDown(aStartMicroseconds);
   aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize);
 }
 
 void MediaDecoderStateMachine::SendStreamData()
 {
   MOZ_ASSERT(OnStateMachineThread(), "Should be on state machine thread");
   AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
+  MOZ_ASSERT(!mAudioSink, "Should've been stopped in CallRunStateMachine()");
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
 
   bool finished =
       (!mInfo.HasAudio() || AudioQueue().IsFinished()) &&
       (!mInfo.HasVideo() || VideoQueue().IsFinished());
   if (mDecoder->IsSameOriginMedia()) {
     SourceMediaStream* mediaStream = stream->mStream;
@@ -405,26 +400,26 @@ void MediaDecoderStateMachine::SendStrea
 
     if (!stream->mStreamInitialized) {
       if (mInfo.HasAudio()) {
         TrackID audioTrackId = mInfo.mAudio.mTrackInfo.mOutputId;
         AudioSegment* audio = new AudioSegment();
         mediaStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio,
                                    SourceMediaStream::ADDTRACK_QUEUED);
         stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
-            TaskQueue(), GetWakeDecoderRunnable());
+            GetStateMachineThread(), GetWakeDecoderRunnable());
         stream->mNextAudioTime = mStartTime + stream->mInitialTime;
       }
       if (mInfo.HasVideo()) {
         TrackID videoTrackId = mInfo.mVideo.mTrackInfo.mOutputId;
         VideoSegment* video = new VideoSegment();
         mediaStream->AddTrack(videoTrackId, 0, video,
                               SourceMediaStream::ADDTRACK_QUEUED);
         stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
-            TaskQueue(), GetWakeDecoderRunnable());
+            GetStateMachineThread(), GetWakeDecoderRunnable());
 
         // TODO: We can't initialize |mNextVideoTime| until |mStartTime|
         // is set. This is a good indication that DecodedStreamData is in
         // deep coupling with the state machine and we should move the class
         // into MediaDecoderStateMachine.
         stream->mNextVideoTime = mStartTime + stream->mInitialTime;
       }
       mediaStream->FinishAddTracks();
@@ -570,17 +565,17 @@ bool MediaDecoderStateMachine::HaveEnoug
 
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishAudio) {
     MOZ_ASSERT(mInfo.HasAudio());
     TrackID audioTrackId = mInfo.mAudio.mTrackInfo.mOutputId;
     if (!stream->mStream->HaveEnoughBuffered(audioTrackId)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
-        TaskQueue(), GetWakeDecoderRunnable());
+        GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
 bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
 {
   AssertCurrentThreadInMonitor();
@@ -593,17 +588,17 @@ bool MediaDecoderStateMachine::HaveEnoug
 
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishVideo) {
     MOZ_ASSERT(mInfo.HasVideo());
     TrackID videoTrackId = mInfo.mVideo.mTrackInfo.mOutputId;
     if (!stream->mStream->HaveEnoughBuffered(videoTrackId)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
-        TaskQueue(), GetWakeDecoderRunnable());
+        GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeVideo()
 {
@@ -855,17 +850,17 @@ MediaDecoderStateMachine::OnNotDecoded(M
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
     MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
     WaitRequestRef(aType).Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                                &MediaDecoderReader::WaitForData, aType)
-      ->RefableThen(TaskQueue(), __func__, this,
+      ->RefableThen(mScheduler.get(), __func__, this,
                     &MediaDecoderStateMachine::OnWaitForDataResolved,
                     &MediaDecoderStateMachine::OnWaitForDataRejected));
     return;
   }
 
   if (aReason == MediaDecoderReader::CANCELED) {
     DispatchDecodeTasksIfNeeded();
     return;
@@ -1084,17 +1079,17 @@ MediaDecoderStateMachine::CheckIfSeekCom
 
   SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
              audioSeekComplete, videoSeekComplete);
 
   if (audioSeekComplete && videoSeekComplete) {
     mDecodeToSeekTarget = false;
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
-    nsresult rv = TaskQueue()->Dispatch(task);
+    nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
     if (NS_FAILED(rv)) {
       DecodeError();
     }
   }
 }
 
 bool
 MediaDecoderStateMachine::IsAudioDecoding()
@@ -1146,17 +1141,20 @@ nsresult MediaDecoderStateMachine::Init(
     return NS_ERROR_FAILURE;
   }
 
   MediaDecoderReader* cloneReader = nullptr;
   if (aCloneDonor) {
     cloneReader = aCloneDonor->mReader;
   }
 
-  nsresult rv = mReader->Init(cloneReader);
+  nsresult rv = mScheduler->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mReader->Init(cloneReader);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::StopPlayback()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -1340,17 +1338,17 @@ int64_t MediaDecoderStateMachine::GetCur
                OnStateMachineThread() ||
                OnDecodeThread(),
                "Should be on main, decode, or state machine thread.");
 
   return mCurrentFrameTime;
 }
 
 bool MediaDecoderStateMachine::IsRealTime() const {
-  return mRealTime;
+  return mScheduler->IsRealTime();
 }
 
 int64_t MediaDecoderStateMachine::GetDuration()
 {
   AssertCurrentThreadInMonitor();
 
   if (mEndTime == -1 || mStartTime == -1)
     return -1;
@@ -1510,18 +1508,19 @@ 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.
-  ScheduleStateMachine();
+  DECODER_LOG("Changed state to SHUTDOWN");
   SetState(DECODER_STATE_SHUTDOWN);
+  mScheduler->ScheduleAndShutdown();
   if (mAudioSink) {
     mAudioSink->PrepareToShutdown();
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
@@ -1770,17 +1769,17 @@ MediaDecoderStateMachine::EnqueueDecodeM
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
-  nsresult rv = TaskQueue()->Dispatch(task);
+  nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
   MOZ_ASSERT(OnDecodeThread());
@@ -1917,17 +1916,17 @@ MediaDecoderStateMachine::InitiateSeek()
 
   // Put a reset in the pipe before seek.
   ResetDecode();
 
   // Do the seek.
   mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                     &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
                                     GetEndTime())
-    ->RefableThen(TaskQueue(), __func__, this,
+    ->RefableThen(mScheduler.get(), __func__, this,
                   &MediaDecoderStateMachine::OnSeekCompleted,
                   &MediaDecoderStateMachine::OnSeekFailed));
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -1965,17 +1964,17 @@ MediaDecoderStateMachine::EnsureAudioDec
     return NS_OK;
   }
 
   SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o",
              AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
 
   mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
                                          __func__, &MediaDecoderReader::RequestAudioData)
-    ->RefableThen(TaskQueue(), __func__, this,
+    ->RefableThen(mScheduler.get(), __func__, this,
                   &MediaDecoderStateMachine::OnAudioDecoded,
                   &MediaDecoderStateMachine::OnAudioNotDecoded));
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
@@ -2025,17 +2024,17 @@ MediaDecoderStateMachine::EnsureVideoDec
 
   SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
              VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
              currentTime);
 
   mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                          &MediaDecoderReader::RequestVideoData,
                                          skipToNextKeyFrame, currentTime)
-    ->RefableThen(TaskQueue(), __func__, this,
+    ->RefableThen(mScheduler.get(), __func__, this,
                   &MediaDecoderStateMachine::OnVideoDecoded,
                   &MediaDecoderStateMachine::OnVideoNotDecoded));
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
 {
@@ -2163,19 +2162,19 @@ MediaDecoderStateMachine::DecodeError()
       DECODER_WARN("Failed to dispatch AcquireMonitorAndInvokeDecodeError");
     }
     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.
-  ScheduleStateMachine();
+  DECODER_WARN("Decode error, changed state to SHUTDOWN due to error");
   SetState(DECODER_STATE_SHUTDOWN);
-  DECODER_WARN("Decode error, changed state to SHUTDOWN due to error");
+  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.
@@ -2318,22 +2317,22 @@ MediaDecoderStateMachine::DecodeFirstFra
   MOZ_ASSERT(OnStateMachineThread());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
   DECODER_LOG("DecodeFirstFrame started");
 
   if (HasAudio()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
-    AudioQueue().AddPopListener(decodeTask, TaskQueue());
+    AudioQueue().AddPopListener(decodeTask, GetStateMachineThread());
   }
   if (HasVideo()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
-    VideoQueue().AddPopListener(decodeTask, TaskQueue());
+    VideoQueue().AddPopListener(decodeTask, GetStateMachineThread());
   }
 
   if (IsRealTime()) {
     SetStartTime(0);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else if (mSentFirstFrameLoadedEvent) {
     // We're resuming from dormant state, so we don't need to request
@@ -2341,25 +2340,25 @@ MediaDecoderStateMachine::DecodeFirstFra
     // we have the start time from last time we loaded.
     SetStartTime(mStartTime);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
                                              __func__, &MediaDecoderReader::RequestAudioData)
-        ->RefableThen(TaskQueue(), __func__, this,
+        ->RefableThen(mScheduler.get(), __func__, this,
                       &MediaDecoderStateMachine::OnAudioDecoded,
                       &MediaDecoderStateMachine::OnAudioNotDecoded));
     }
     if (HasVideo()) {
       mVideoDecodeStartTime = TimeStamp::Now();
       mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
                                              __func__, &MediaDecoderReader::RequestVideoData, false, int64_t(0))
-        ->RefableThen(TaskQueue(), __func__, this,
+        ->RefableThen(mScheduler.get(), __func__, this,
                       &MediaDecoderStateMachine::OnVideoDecoded,
                       &MediaDecoderStateMachine::OnVideoNotDecoded));
     }
   }
 
   return NS_OK;
 }
 
@@ -2556,18 +2555,16 @@ class nsDecoderDisposeEvent : public nsR
 public:
   nsDecoderDisposeEvent(already_AddRefed<MediaDecoder> aDecoder,
                         already_AddRefed<MediaDecoderStateMachine> aStateMachine)
     : mDecoder(aDecoder), mStateMachine(aStateMachine) {}
   NS_IMETHOD Run() {
     NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
     MOZ_ASSERT(mStateMachine);
     MOZ_ASSERT(mDecoder);
-    mStateMachine->TaskQueue()->BeginShutdown();
-    mStateMachine->TaskQueue()->AwaitShutdownAndIdle();
     mStateMachine->BreakCycles();
     mDecoder->BreakCycles();
     mStateMachine = nullptr;
     mDecoder = nullptr;
     return NS_OK;
   }
 private:
   nsRefPtr<MediaDecoder> mDecoder;
@@ -2592,17 +2589,17 @@ private:
   nsRefPtr<MediaDecoder> mDecoder;
   nsRefPtr<MediaDecoderStateMachine> mStateMachine;
 };
 
 void
 MediaDecoderStateMachine::ShutdownReader()
 {
   MOZ_ASSERT(OnDecodeThread());
-  mReader->Shutdown()->Then(TaskQueue(), __func__, this,
+  mReader->Shutdown()->Then(mScheduler.get(), __func__, this,
                             &MediaDecoderStateMachine::FinishShutdown,
                             &MediaDecoderStateMachine::FinishShutdown);
 }
 
 void
 MediaDecoderStateMachine::FinishShutdown()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -2628,34 +2625,25 @@ MediaDecoderStateMachine::FinishShutdown
   // hold the decoder monitor here. We also want to guarantee that the
   // state machine is destroyed on the main thread, and so the
   // event runner running this function (which holds a reference to the
   // 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.
-  RefPtr<nsIRunnable> task = new nsDispatchDisposeEvent(mDecoder, this);
-  TaskQueue()->Dispatch(task);
+  GetStateMachineThread()->Dispatch(
+    new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
 
   DECODER_LOG("Dispose Event Dispatched");
 }
 
 nsresult MediaDecoderStateMachine::RunStateMachine()
 {
-  MOZ_ASSERT(OnStateMachineThread());
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  mDelayedScheduler.Reset(); // Must happen on state machine thread.
-  mDispatchedStateMachine = false;
-
-  // If audio is being captured, stop the audio sink if it's running
-  if (mAudioCaptured) {
-    StopAudioThread();
-  }
+  AssertCurrentThreadInMonitor();
 
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
 
   switch (mState) {
     case DECODER_STATE_SHUTDOWN: {
       mQueuedSeek.RejectIfExists(__func__);
       mPendingSeek.RejectIfExists(__func__);
@@ -2746,17 +2734,17 @@ nsresult MediaDecoderStateMachine::RunSt
               elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
               (mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs)
                                : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
               mDecoder->IsExpectingMoreData())
         {
           DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
                       mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
                       (mQuickBuffering ? "(quick exit)" : ""));
-          ScheduleStateMachineIn(USECS_PER_S);
+          ScheduleStateMachine(USECS_PER_S);
           return NS_OK;
         }
       } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
         MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                    "Don't yet have a strategy for non-heuristic + non-WaitForData");
         DispatchDecodeTasksIfNeeded();
         MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioDataRequest.Exists() || mAudioWaitRequest.Exists());
         MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoDataRequest.Exists() || mVideoWaitRequest.Exists());
@@ -3070,17 +3058,17 @@ void MediaDecoderStateMachine::AdvanceFr
     if (shouldBuffer) {
       if (currentFrame) {
         VideoQueue().PushFront(currentFrame);
       }
       StartBuffering();
       // Don't go straight back to the state machine loop since that might
       // cause us to start decoding again and we could flip-flop between
       // decoding and quick-buffering.
-      ScheduleStateMachineIn(USECS_PER_S);
+      ScheduleStateMachine(USECS_PER_S);
       return;
     }
   }
 
   // We've got enough data to keep playing until at least the next frame.
   // Start playing now if need be.
   if ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0) {
     MaybeStartPlayback();
@@ -3132,22 +3120,17 @@ void MediaDecoderStateMachine::AdvanceFr
   }
 
   // If the number of audio/video frames queued has changed, either by
   // this function popping and playing a video frame, or by the audio
   // thread popping and playing an audio frame, we may need to update our
   // ready state. Post an update to do so.
   UpdateReadyState();
 
-  int64_t delay = remainingTime / mPlaybackRate;
-  if (delay > 0) {
-    ScheduleStateMachineIn(delay);
-  } else {
-    ScheduleStateMachine();
-  }
+  ScheduleStateMachine(remainingTime / mPlaybackRate);
 }
 
 nsresult
 MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
 {
   nsRefPtr<VideoData> video(aSample);
   MOZ_ASSERT(video);
   DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
@@ -3383,84 +3366,63 @@ void MediaDecoderStateMachine::SetPlaySt
   }
   if (!mPlayStartTime.IsNull()) {
     mAudioSink->StartPlayback();
   } else {
     mAudioSink->StopPlayback();
   }
 }
 
+nsresult MediaDecoderStateMachine::CallRunStateMachine()
+{
+  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();
+  }
+
+  return RunStateMachine();
+}
+
+nsresult MediaDecoderStateMachine::TimeoutExpired(void* aClosure)
+{
+  MediaDecoderStateMachine* p = static_cast<MediaDecoderStateMachine*>(aClosure);
+  return p->CallRunStateMachine();
+}
+
 void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DispatchAudioDecodeTaskIfNeeded();
   DispatchVideoDecodeTaskIfNeeded();
 }
 
-void
-MediaDecoderStateMachine::ScheduleStateMachine() {
-  AssertCurrentThreadInMonitor();
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    NS_WARNING("Refusing to schedule shutdown state machine");
-    return;
-  }
-
-  if (mDispatchedStateMachine) {
-    return;
-  }
-  mDispatchedStateMachine = true;
-
-  RefPtr<nsIRunnable> task =
-    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::RunStateMachine);
-  nsresult rv = TaskQueue()->Dispatch(task);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  (void) rv;
-}
-
-void
-MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds)
-{
-  AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(OnStateMachineThread()); // mDelayedScheduler.Ensure() may Disconnect()
-                                      // the promise, which must happen on the state
-                                      // machine thread.
-  MOZ_ASSERT(aMicroseconds > 0);
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    NS_WARNING("Refusing to schedule shutdown state machine");
-    return;
-  }
-
-  if (mDispatchedStateMachine) {
-    return;
-  }
-
-  // Real-time weirdness.
-  if (IsRealTime()) {
-    aMicroseconds = std::min(aMicroseconds, int64_t(40000));
-  }
-
-  TimeStamp now = TimeStamp::Now();
-  TimeStamp target = now + TimeDuration::FromMicroseconds(aMicroseconds);
-
-  SAMPLE_LOG("Scheduling state machine for %lf ms from now", (target - now).ToMilliseconds());
-  mDelayedScheduler.Ensure(target);
+nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
+  return mScheduler->Schedule(aUsecs);
 }
 
 bool MediaDecoderStateMachine::OnDecodeThread() const
 {
   return !DecodeTaskQueue() || DecodeTaskQueue()->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::OnStateMachineThread() const
 {
-  return TaskQueue()->IsCurrentThreadIn();
+  return mScheduler->OnStateMachineThread();
+}
+
+nsIEventTarget* MediaDecoderStateMachine::GetStateMachineThread() const
+{
+  return mScheduler->GetStateMachineThread();
 }
 
 bool MediaDecoderStateMachine::IsStateMachineScheduled() const
 {
-  return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
+  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/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -84,18 +84,18 @@ hardware (via AudioStream).
 
 #include "mozilla/Attributes.h"
 #include "nsThreadUtils.h"
 #include "MediaDecoder.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderOwner.h"
 #include "MediaMetadataManager.h"
+#include "MediaDecoderStateMachineScheduler.h"
 #include "mozilla/RollingMean.h"
-#include "MediaTimer.h"
 
 class nsITimer;
 
 namespace mozilla {
 
 class AudioSegment;
 class VideoSegment;
 class MediaTaskQueue;
@@ -206,18 +206,18 @@ public:
   MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
 
   // Cause state transitions. These methods obtain the decoder monitor
   // to synchronise the change of state, and to notify other threads
   // that the state has changed.
   void Play()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    RefPtr<nsRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal);
-    TaskQueue()->Dispatch(r);
+    nsRefPtr<nsRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal);
+    GetStateMachineThread()->Dispatch(r, NS_DISPATCH_NORMAL);
   }
 
 private:
   // The actual work for the above, which happens asynchronously on the state
   // machine thread.
   void PlayInternal();
 public:
 
@@ -306,40 +306,31 @@ public:
     if (mReader) {
       return mReader->SizeOfAudioQueueInBytes();
     }
     return 0;
   }
 
   void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
-  // Returns the state machine task queue.
-  MediaTaskQueue* TaskQueue() const { return mTaskQueue; }
+  // Returns the shared state machine thread.
+  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.
-  void ScheduleStateMachine();
-
-  // Invokes ScheduleStateMachine to run in |aMicroseconds| microseconds,
-  // unless it's already scheduled to run earlier, in which case the
-  // request is discarded.
-  void ScheduleStateMachineIn(int64_t aMicroseconds);
+  // 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);
 
-  void OnDelayedSchedule()
-  {
-    MOZ_ASSERT(OnStateMachineThread());
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDelayedScheduler.CompleteRequest();
-    ScheduleStateMachine();
-  }
-
-  void NotReached() { MOZ_DIAGNOSTIC_ASSERT(false); }
+  // 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();
@@ -693,16 +684,19 @@ protected:
   // The decoder monitor must be held.
   void CheckIfDecodeComplete();
 
   // Copy audio from an AudioData packet to aOutput. This may require
   // inserting silence depending on the timing of the audio packet.
   void SendStreamAudio(AudioData* aAudio, DecodedStreamData* aStream,
                        AudioSegment* aOutput);
 
+  // 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;
 
   // Returns true if we're not playing and the decode thread has filled its
@@ -746,70 +740,19 @@ 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;
 
-  // Task queue for running the state machine.
-  nsRefPtr<MediaTaskQueue> mTaskQueue;
-
-  // True is we are decoding a realtime stream, like a camera stream.
-  bool mRealTime;
-
-  // True if we've dispatched a task to run the state machine but the task has
-  // yet to run.
-  bool mDispatchedStateMachine;
-
-  // Class for managing delayed dispatches of the state machine.
-  class DelayedScheduler {
-  public:
-    explicit DelayedScheduler(MediaDecoderStateMachine* aSelf)
-      : mSelf(aSelf), mMediaTimer(new MediaTimer()) {}
-
-    bool IsScheduled() const { return !mTarget.IsNull(); }
-
-    void Reset()
-    {
-      MOZ_ASSERT(mSelf->OnStateMachineThread(),
-                 "Must be on state machine queue to disconnect");
-      if (IsScheduled()) {
-        mRequest.Disconnect();
-        mTarget = TimeStamp();
-      }
-    }
-
-    void Ensure(mozilla::TimeStamp& aTarget)
-    {
-      if (IsScheduled() && mTarget <= aTarget) {
-        return;
-      }
-      Reset();
-      mTarget = aTarget;
-      mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->RefableThen(
-        mSelf->TaskQueue(), __func__, mSelf,
-        &MediaDecoderStateMachine::OnDelayedSchedule,
-        &MediaDecoderStateMachine::NotReached));
-    }
-
-    void CompleteRequest()
-    {
-      mRequest.Complete();
-      mTarget = TimeStamp();
-    }
-
-  private:
-    MediaDecoderStateMachine* mSelf;
-    nsRefPtr<MediaTimer> mMediaTimer;
-    MediaPromiseConsumerHolder<mozilla::MediaTimerPromise> mRequest;
-    TimeStamp mTarget;
-
-  } mDelayedScheduler;
+  // Used to schedule state machine cycles. This should never outlive
+  // the life cycle of the state machine.
+  const nsRefPtr<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.
@@ -1007,28 +950,28 @@ protected:
   // At the start of decoding we want to "preroll" the decode until we've
   // got a few frames decoded before we consider whether decode is falling
   // behind. Otherwise our "we're falling behind" logic will trigger
   // unneccessarily if we start playing as soon as the first sample is
   // decoded. These two fields store how many video frames and audio
   // samples we must consume before are considered to be finished prerolling.
   uint32_t AudioPrerollUsecs() const
   {
-    if (IsRealTime()) {
+    if (mScheduler->IsRealTime()) {
       return 0;
     }
 
     uint32_t result = mLowAudioThresholdUsecs * 2;
     MOZ_ASSERT(result <= mAmpleAudioThresholdUsecs, "Prerolling will never finish");
     return result;
   }
 
   uint32_t VideoPrerollFrames() const
   {
-    return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2;
+    return mScheduler->IsRealTime() ? 0 : GetAmpleVideoFrames() / 2;
   }
 
   bool DonePrerollingAudio()
   {
     AssertCurrentThreadInMonitor();
     return !IsAudioDecoding() || GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate;
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaDecoderStateMachineScheduler.cpp
@@ -0,0 +1,194 @@
+/* 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"
+
+namespace {
+class TimerEvent : public nsITimerCallback, public nsRunnable {
+  typedef mozilla::MediaDecoderStateMachineScheduler Scheduler;
+  NS_DECL_ISUPPORTS_INHERITED
+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_INHERITED(TimerEvent, nsRunnable, nsITimerCallback);
+} // 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 (NS_WARN_IF(mState == SCHEDULER_STATE_SHUTDOWN)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aUsecs = std::max<int64_t>(aUsecs, 0);
+
+  TimeStamp timeout = TimeStamp::Now() +
+    TimeDuration::FromMilliseconds(static_cast<double>(aUsecs) / USECS_PER_MS);
+
+  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/dom/media/MediaDecoderStateMachineScheduler.h
@@ -0,0 +1,78 @@
+/* -*- 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:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachineScheduler)
+  MediaDecoderStateMachineScheduler(ReentrantMonitor& aMonitor,
+                                    nsresult (*aTimeoutCallback)(void*),
+                                    void* aClosure, bool aRealTime);
+  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:
+  ~MediaDecoderStateMachineScheduler();
+  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/dom/media/MediaPromise.cpp
+++ b/dom/media/MediaPromise.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "MediaPromise.h"
 
+#include "MediaDecoderStateMachineScheduler.h"
 #include "MediaTaskQueue.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace detail {
 
 nsresult
 DispatchMediaPromiseRunnable(MediaTaskQueue* aTaskQueue, nsIRunnable* aRunnable)
@@ -19,23 +20,35 @@ DispatchMediaPromiseRunnable(MediaTaskQu
 }
 
 nsresult
 DispatchMediaPromiseRunnable(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable)
 {
   return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
 }
 
+nsresult
+DispatchMediaPromiseRunnable(MediaDecoderStateMachineScheduler* aScheduler, nsIRunnable* aRunnable)
+{
+  return aScheduler->GetStateMachineThread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
 void
 AssertOnThread(MediaTaskQueue* aQueue)
 {
   MOZ_ASSERT(aQueue->IsCurrentThreadIn());
 }
 
 void AssertOnThread(nsIEventTarget* aTarget)
 {
   nsCOMPtr<nsIThread> targetThread = do_QueryInterface(aTarget);
   MOZ_ASSERT(targetThread, "Don't know how to deal with threadpools etc here");
   MOZ_ASSERT(NS_GetCurrentThread() == targetThread);
 }
 
+void
+AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler)
+{
+  MOZ_ASSERT(aScheduler->OnStateMachineThread());
+}
+
 }
 } // namespace mozilla
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -11,41 +11,43 @@
 
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Monitor.h"
-#include "mozilla/unused.h"
 
 /* Polyfill __func__ on MSVC for consumers to pass to the MediaPromise API. */
 #ifdef _MSC_VER
 #define __func__ __FUNCTION__
 #endif
 
 class nsIEventTarget;
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaPromiseLog;
 
 #define PROMISE_LOG(x, ...) \
   MOZ_ASSERT(gMediaPromiseLog); \
   PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
 
 class MediaTaskQueue;
+class MediaDecoderStateMachineScheduler;
 namespace detail {
 
 nsresult DispatchMediaPromiseRunnable(MediaTaskQueue* aQueue, nsIRunnable* aRunnable);
 nsresult DispatchMediaPromiseRunnable(nsIEventTarget* aTarget, nsIRunnable* aRunnable);
+nsresult DispatchMediaPromiseRunnable(MediaDecoderStateMachineScheduler* aScheduler, nsIRunnable* aRunnable);
 
 #ifdef DEBUG
 void AssertOnThread(MediaTaskQueue* aQueue);
 void AssertOnThread(nsIEventTarget* aTarget);
+void AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler);
 #endif
 
 } // namespace detail
 
 /*
  * A promise manages an asynchronous request that may or may not be able to be
  * fulfilled immediately. When an API returns a promise, the consumer may attach
  * callbacks to be invoked (asynchronously, on a specified thread) when the
@@ -101,21 +103,28 @@ public:
     return Move(p);
   }
 
   class Consumer
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Consumer)
 
-    virtual void Disconnect() = 0;
+    void Disconnect()
+    {
+      AssertOnDispatchThread();
+      MOZ_DIAGNOSTIC_ASSERT(!mComplete);
+      mDisconnected = true;
+    }
 
-    // MSVC complains when an inner class (ThenValueBase::{Resolve,Reject}Runnable)
-    // tries to access an inherited protected member.
-    bool IsDisconnected() const { return mDisconnected; }
+#ifdef DEBUG
+    virtual void AssertOnDispatchThread() = 0;
+#else
+    void AssertOnDispatchThread() {}
+#endif
 
   protected:
     Consumer() : mComplete(false), mDisconnected(false) {}
     virtual ~Consumer() {}
 
     bool mComplete;
     bool mDisconnected;
   };
@@ -135,17 +144,17 @@ protected:
     {
     public:
       ResolveRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue)
         : mThenValue(aThenValue)
         , mResolveValue(aResolveValue) {}
 
       ~ResolveRunnable()
       {
-        MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected());
+        MOZ_ASSERT(!mThenValue);
       }
 
       NS_IMETHODIMP Run()
       {
         PROMISE_LOG("ResolveRunnable::Run() [this=%p]", this);
         mThenValue->DoResolve(mResolveValue);
         mThenValue = nullptr;
         return NS_OK;
@@ -160,17 +169,17 @@ protected:
     {
     public:
       RejectRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue)
         : mThenValue(aThenValue)
         , mRejectValue(aRejectValue) {}
 
       ~RejectRunnable()
       {
-        MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected());
+        MOZ_ASSERT(!mThenValue);
       }
 
       NS_IMETHODIMP Run()
       {
         PROMISE_LOG("RejectRunnable::Run() [this=%p]", this);
         mThenValue->DoReject(mRejectValue);
         mThenValue = nullptr;
         return NS_OK;
@@ -238,87 +247,71 @@ protected:
       MOZ_ASSERT(!aPromise->IsPending());
       bool resolved = aPromise->mResolveValue.isSome();
       nsRefPtr<nsRunnable> runnable =
         resolved ? static_cast<nsRunnable*>(new (typename ThenValueBase::ResolveRunnable)(this, aPromise->mResolveValue.ref()))
                  : static_cast<nsRunnable*>(new (typename ThenValueBase::RejectRunnable)(this, aPromise->mRejectValue.ref()));
       PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
                   resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
                   runnable.get(), aPromise, this);
-      nsresult rv = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable);
-      unused << rv;
-
-      // NB: mDisconnected is only supposed to be accessed on the dispatch
-      // thread. However, we require the consumer to have disconnected any
-      // oustanding promise requests _before_ initiating shutdown on the
-      // thread or task queue. So the only non-buggy scenario for dispatch
-      // failing involves the target thread being unable to manipulate the
-      // ThenValue (since it's been disconnected), so it's safe to read here.
-      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) || Consumer::mDisconnected);
+      DebugOnly<nsresult> rv = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
 
 #ifdef DEBUG
-  void AssertOnDispatchThread()
+  virtual void AssertOnDispatchThread() MOZ_OVERRIDE
   {
     detail::AssertOnThread(mResponseTarget);
   }
-#else
-  void AssertOnDispatchThread() {}
 #endif
 
-  virtual void Disconnect() MOZ_OVERRIDE
-  {
-    AssertOnDispatchThread();
-    MOZ_DIAGNOSTIC_ASSERT(!Consumer::mComplete);
-    Consumer::mDisconnected = true;
-
-    // If a Consumer has been disconnected, we don't guarantee that the
-    // resolve/reject runnable will be dispatched. Null out our refcounted
-    // this-value now so that it's released predictably on the dispatch thread.
-    mThisVal = nullptr;
-  }
-
   protected:
     virtual void DoResolve(ResolveValueType aResolveValue) MOZ_OVERRIDE
     {
       Consumer::mComplete = true;
       if (Consumer::mDisconnected) {
-        MOZ_ASSERT(!mThisVal);
         PROMISE_LOG("ThenValue::DoResolve disconnected - bailing out [this=%p]", this);
+        // Null these out for the same reasons described below.
+        mResponseTarget = nullptr;
+        mThisVal = nullptr;
         return;
       }
       InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aResolveValue);
 
-      // Null out mThisVal after invoking the callback so that any references are
-      // released predictably on the dispatch thread. Otherwise, it would be
+      // Null these out after invoking the callback so that any references are
+      // released predictably on the target thread. Otherwise, they would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
+      mResponseTarget = nullptr;
       mThisVal = nullptr;
     }
 
     virtual void DoReject(RejectValueType aRejectValue) MOZ_OVERRIDE
     {
       Consumer::mComplete = true;
       if (Consumer::mDisconnected) {
-        MOZ_ASSERT(!mThisVal);
         PROMISE_LOG("ThenValue::DoReject disconnected - bailing out [this=%p]", this);
+        // Null these out for the same reasons described below.
+        mResponseTarget = nullptr;
+        mThisVal = nullptr;
         return;
       }
       InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aRejectValue);
 
-      // Null out mThisVal after invoking the callback so that any references are
-      // released predictably on the dispatch thread. Otherwise, it would be
+      // Null these out after invoking the callback so that any references are
+      // released predictably on the target thread. Otherwise, they would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
+      mResponseTarget = nullptr;
       mThisVal = nullptr;
     }
 
   private:
-    nsRefPtr<TargetType> mResponseTarget; // May be released on any thread.
-    nsRefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
+    nsRefPtr<TargetType> mResponseTarget;
+    nsRefPtr<ThisType> mThisVal;
     ResolveMethodType mResolveMethod;
     RejectMethodType mRejectMethod;
   };
 public:
 
   template<typename TargetType, typename ThisType,
            typename ResolveMethodType, typename RejectMethodType>
   already_AddRefed<Consumer> RefableThen(TargetType* aResponseTarget, const char* aCallSite, ThisType* aThisVal,
@@ -663,17 +656,17 @@ private:
 template<typename PromiseType, typename TargetType>
 static nsRefPtr<PromiseType>
 ProxyInternal(TargetType* aTarget, MethodCallBase<PromiseType>* aMethodCall, const char* aCallerName)
 {
   nsRefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName);
   nsRefPtr<ProxyRunnable<PromiseType>> r = new ProxyRunnable<PromiseType>(p, aMethodCall);
   nsresult rv = detail::DispatchMediaPromiseRunnable(aTarget, r);
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  unused << rv;
+  (void) rv; // Avoid compilation failures in builds with MOZ_DIAGNOSTIC_ASSERT disabled.
   return Move(p);
 }
 
 } // namespace detail
 
 template<typename PromiseType, typename TargetType, typename ThisType>
 static nsRefPtr<PromiseType>
 ProxyMediaCall(TargetType* aTarget, ThisType* aThisVal, const char* aCallerName,
--- a/dom/media/MediaQueue.h
+++ b/dom/media/MediaQueue.h
@@ -152,45 +152,45 @@ template <class T> class MediaQueue : pr
     return frames;
   }
 
   void ClearListeners() {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mPopListeners.Clear();
   }
 
-  void AddPopListener(nsIRunnable* aRunnable, MediaTaskQueue* aTarget) {
+  void AddPopListener(nsIRunnable* aRunnable, nsIEventTarget* aTarget) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mPopListeners.AppendElement(Listener(aRunnable, aTarget));
   }
 
 private:
   mutable ReentrantMonitor mReentrantMonitor;
 
   struct Listener {
-    Listener(nsIRunnable* aRunnable, MediaTaskQueue* aTarget)
+    Listener(nsIRunnable* aRunnable, nsIEventTarget* aTarget)
       : mRunnable(aRunnable)
       , mTarget(aTarget)
     {
     }
     Listener(const Listener& aOther)
       : mRunnable(aOther.mRunnable)
       , mTarget(aOther.mTarget)
     {
     }
     RefPtr<nsIRunnable> mRunnable;
-    RefPtr<MediaTaskQueue> mTarget;
+    RefPtr<nsIEventTarget> mTarget;
   };
 
   nsTArray<Listener> mPopListeners;
 
   void NotifyPopListeners() {
     for (uint32_t i = 0; i < mPopListeners.Length(); i++) {
       Listener& l = mPopListeners[i];
-      l.mTarget->Dispatch(l.mRunnable);
+      l.mTarget->Dispatch(l.mRunnable, NS_DISPATCH_NORMAL);
     }
   }
 
   // True when we've decoded the last frame of data in the
   // bitstream for which we're queueing frame data.
   bool mEndOfStream;
 };
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -273,17 +273,17 @@ MediaStreamGraphImpl::UpdateBufferSuffic
       data->mHaveEnough = track->GetEnd() >= desiredEnd;
       if (!data->mHaveEnough) {
         runnables.MoveElementsFrom(data->mDispatchWhenNotEnough);
       }
     }
   }
 
   for (uint32_t i = 0; i < runnables.Length(); ++i) {
-    runnables[i].mTarget->Dispatch(runnables[i].mRunnable);
+    runnables[i].mTarget->Dispatch(runnables[i].mRunnable, 0);
   }
 }
 
 StreamTime
 MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
                                             GraphTime aTime)
 {
   MOZ_ASSERT(aTime <= CurrentDriver()->StateComputedTime(),
@@ -2471,31 +2471,31 @@ SourceMediaStream::GetEndOfAppendedData(
     return track->mEndOfFlushedData + track->mData->GetDuration();
   }
   NS_ERROR("Track not found");
   return 0;
 }
 
 void
 SourceMediaStream::DispatchWhenNotEnoughBuffered(TrackID aID,
-    MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable)
+    nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable)
 {
   MutexAutoLock lock(mMutex);
   TrackData* data = FindDataForTrack(aID);
   if (!data) {
-    aSignalQueue->Dispatch(aSignalRunnable);
+    aSignalThread->Dispatch(aSignalRunnable, 0);
     return;
   }
 
   if (data->mHaveEnough) {
     if (data->mDispatchWhenNotEnough.IsEmpty()) {
-      data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalQueue, aSignalRunnable);
+      data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalThread, aSignalRunnable);
     }
   } else {
-    aSignalQueue->Dispatch(aSignalRunnable);
+    aSignalThread->Dispatch(aSignalRunnable, 0);
   }
 }
 
 void
 SourceMediaStream::EndTrack(TrackID aID)
 {
   MutexAutoLock lock(mMutex);
   TrackData *track = FindDataForTrack(aID);
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -11,17 +11,16 @@
 #include "AudioStream.h"
 #include "nsTArray.h"
 #include "nsIRunnable.h"
 #include "StreamBuffer.h"
 #include "TimeVarying.h"
 #include "VideoFrameContainer.h"
 #include "VideoSegment.h"
 #include "MainThreadUtils.h"
-#include "MediaTaskQueue.h"
 #include "nsAutoRef.h"
 #include "GraphDriver.h"
 #include <speex/speex_resampler.h>
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "DOMMediaStream.h"
 
 class nsIRunnable;
 
@@ -779,17 +778,17 @@ public:
   StreamTime GetEndOfAppendedData(TrackID aID);
   /**
    * Ensures that aSignalRunnable will be dispatched to aSignalThread
    * when we don't have enough buffered data in the track (which could be
    * immediately). Will dispatch the runnable immediately if the track
    * does not exist. No op if a runnable is already present for this track.
    */
   void DispatchWhenNotEnoughBuffered(TrackID aID,
-      MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable);
+      nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable);
   /**
    * Indicate that a track has ended. Do not do any more API calls
    * affecting this track.
    * Ignored if the track does not exist.
    */
   void EndTrack(TrackID aID);
   /**
    * Indicate that no tracks will be added starting before time aKnownTime.
@@ -843,24 +842,24 @@ public:
   void RegisterForAudioMixing();
 
   // XXX need a Reset API
 
   friend class MediaStreamGraphImpl;
 
 protected:
   struct ThreadAndRunnable {
-    void Init(MediaTaskQueue* aTarget, nsIRunnable* aRunnable)
+    void Init(nsIEventTarget* aTarget, nsIRunnable* aRunnable)
     {
       mTarget = aTarget;
       mRunnable = aRunnable;
     }
 
-    nsRefPtr<MediaTaskQueue> mTarget;
-    RefPtr<nsIRunnable> mRunnable;
+    nsCOMPtr<nsIEventTarget> mTarget;
+    nsCOMPtr<nsIRunnable> mRunnable;
   };
   enum TrackCommands {
     TRACK_CREATE = MediaStreamListener::TRACK_EVENT_CREATED,
     TRACK_END = MediaStreamListener::TRACK_EVENT_ENDED
   };
   /**
    * Data for each track that hasn't ended.
    */
deleted file mode 100644
--- a/dom/media/MediaTimer.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/* -*- 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/. */
-
-#include "MediaTimer.h"
-
-#include <math.h>
-
-#include "nsComponentManagerUtils.h"
-#include "nsThreadUtils.h"
-
-namespace mozilla {
-
-NS_IMPL_ADDREF(MediaTimer)
-NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy())
-
-MediaTimer::MediaTimer()
-  : mMonitor("MediaTimer Monitor")
-  , mTimer(do_CreateInstance("@mozilla.org/timer;1"))
-  , mUpdateScheduled(false)
-{
-  // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
-  // thread, which is equivalent to an nsIThread for our purposes.
-  RefPtr<SharedThreadPool> threadPool(
-    SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1));
-  mThread = threadPool.get();
-  mTimer->SetTarget(mThread);
-}
-
-void
-MediaTimer::DispatchDestroy()
-{
-  nsCOMPtr<nsIRunnable> task = NS_NewNonOwningRunnableMethod(this, &MediaTimer::Destroy);
-  nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  (void) rv;
-}
-
-void
-MediaTimer::Destroy()
-{
-  MOZ_ASSERT(OnMediaTimerThread());
-
-  // Reject any outstanding entries. There's no need to acquire the monitor
-  // here, because we're on the timer thread and all other references to us
-  // must be gone.
-  while (!mEntries.empty()) {
-    mEntries.top().mPromise->Reject(false, __func__);
-    mEntries.pop();
-  }
-
-  // Cancel the timer if necessary.
-  CancelTimerIfArmed();
-
-  delete this;
-}
-
-bool
-MediaTimer::OnMediaTimerThread()
-{
-  bool rv = false;
-  mThread->IsOnCurrentThread(&rv);
-  return rv;
-}
-
-nsRefPtr<MediaTimerPromise>
-MediaTimer::WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite)
-{
-  MonitorAutoLock mon(mMonitor);
-  Entry e(aTimeStamp, aCallSite);
-  nsRefPtr<MediaTimerPromise> p = e.mPromise.get();
-  mEntries.push(e);
-  ScheduleUpdate();
-  return p;
-}
-
-void
-MediaTimer::ScheduleUpdate()
-{
-  mMonitor.AssertCurrentThreadOwns();
-  if (mUpdateScheduled) {
-    return;
-  }
-  mUpdateScheduled = true;
-
-  nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(this, &MediaTimer::Update);
-  nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  (void) rv;
-}
-
-void
-MediaTimer::Update()
-{
-  MonitorAutoLock mon(mMonitor);
-  UpdateLocked();
-}
-
-void
-MediaTimer::UpdateLocked()
-{
-  MOZ_ASSERT(OnMediaTimerThread());
-  mMonitor.AssertCurrentThreadOwns();
-  mUpdateScheduled = false;
-
-  // Resolve all the promises whose time is up.
-  TimeStamp now = TimeStamp::Now();
-  while (!mEntries.empty() && mEntries.top().mTimeStamp < now) {
-    mEntries.top().mPromise->Resolve(true, __func__);
-    mEntries.pop();
-  }
-
-  // If we've got no more entries, cancel any pending timer and bail out.
-  if (mEntries.empty()) {
-    CancelTimerIfArmed();
-    return;
-  }
-
-  // We've got more entries - (re)arm the timer for the soonest one.
-  if (!TimerIsArmed() || mEntries.top().mTimeStamp < mCurrentTimerTarget) {
-    CancelTimerIfArmed();
-    ArmTimer(mEntries.top().mTimeStamp, now);
-  }
-}
-
-/*
- * We use a callback function, rather than a callback method, to ensure that
- * the nsITimer does not artifically keep the refcount of the MediaTimer above
- * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so that
- * we never fire against a dangling closure.
- */
-
-/* static */ void
-MediaTimer::TimerCallback(nsITimer* aTimer, void* aClosure)
-{
-  static_cast<MediaTimer*>(aClosure)->TimerFired();
-}
-
-void
-MediaTimer::TimerFired()
-{
-  MonitorAutoLock mon(mMonitor);
-  MOZ_ASSERT(OnMediaTimerThread());
-  mCurrentTimerTarget = TimeStamp();
-  UpdateLocked();
-}
-
-void
-MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow)
-{
-  MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
-  MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow);
-
-  // XPCOM timer resolution is in milliseconds. It's important to never resolve
-  // a timer when mTarget might compare < now (even if very close), so round up.
-  unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds());
-  mCurrentTimerTarget = aTarget;
-  nsresult rv = mTimer->InitWithFuncCallback(&TimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  (void) rv;
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/MediaTimer.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* -*- 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/. */
-
-#if !defined(MediaTimer_h_)
-#define MediaTimer_h_
-
-#include "MediaPromise.h"
-
-#include <queue>
-
-#include "nsITimer.h"
-#include "nsRefPtr.h"
-
-#include "mozilla/Monitor.h"
-#include "mozilla/TimeStamp.h"
-
-namespace mozilla {
-
-// This promise type is only exclusive because so far there isn't a reason for
-// it not to be. Feel free to change that.
-typedef MediaPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise;
-
-// Timers only know how to fire at a given thread, which creates an impedence
-// mismatch with code that operates with MediaTaskQueues. This class solves
-// that mismatch with a dedicated (but shared) thread and a nice MediaPromise-y
-// interface.
-class MediaTimer
-{
-public:
-  MediaTimer();
-
-  // We use a release with a custom Destroy().
-  NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
-  NS_IMETHOD_(MozExternalRefCountType) Release(void);
-
-  nsRefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite);
-
-private:
-  virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); }
-
-  void DispatchDestroy(); // Invoked by Release on an arbitrary thread.
-  void Destroy(); // Runs on the timer thread.
-
-  bool OnMediaTimerThread();
-  void ScheduleUpdate();
-  void Update();
-  void UpdateLocked();
-
-  static void TimerCallback(nsITimer* aTimer, void* aClosure);
-  void TimerFired();
-  void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow);
-
-  bool TimerIsArmed()
-  {
-    return !mCurrentTimerTarget.IsNull();
-  }
-
-  void CancelTimerIfArmed()
-  {
-    MOZ_ASSERT(OnMediaTimerThread());
-    if (TimerIsArmed()) {
-      mTimer->Cancel();
-      mCurrentTimerTarget = TimeStamp();
-    }
-  }
-
-
-  struct Entry
-  {
-    TimeStamp mTimeStamp;
-    nsRefPtr<MediaTimerPromise::Private> mPromise;
-
-    explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite)
-      : mTimeStamp(aTimeStamp)
-      , mPromise(new MediaTimerPromise::Private(aCallSite))
-    {}
-
-    bool operator<(const Entry& aOther) const
-    {
-      return mTimeStamp < aOther.mTimeStamp;
-    }
-  };
-
-  ThreadSafeAutoRefCnt mRefCnt;
-  NS_DECL_OWNINGTHREAD
-  nsCOMPtr<nsIEventTarget> mThread;
-  std::priority_queue<Entry> mEntries;
-  Monitor mMonitor;
-  nsCOMPtr<nsITimer> mTimer;
-  TimeStamp mCurrentTimerTarget;
-  bool mUpdateScheduled;
-};
-
-} // namespace mozilla
-
-#endif
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -97,26 +97,26 @@ EXPORTS += [
     'GraphDriver.h',
     'Latency.h',
     'MediaCache.h',
     'MediaData.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
     'MediaDecoderStateMachine.h',
+    'MediaDecoderStateMachineScheduler.h',
     'MediaInfo.h',
     'MediaMetadataManager.h',
     'MediaPromise.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'MediaTaskQueue.h',
-    'MediaTimer.h',
     'MediaTrack.h',
     'MediaTrackList.h',
     'MP3FrameParser.h',
     'nsIDocumentActivity.h',
     'RtspMediaResource.h',
     'SelfRef.h',
     'SharedBuffer.h',
     'SharedThreadPool.h',
@@ -176,27 +176,27 @@ UNIFIED_SOURCES += [
     'GetUserMediaRequest.cpp',
     'GraphDriver.cpp',
     'Latency.cpp',
     'MediaCache.cpp',
     'MediaData.cpp',
     'MediaDecoder.cpp',
     'MediaDecoderReader.cpp',
     'MediaDecoderStateMachine.cpp',
+    'MediaDecoderStateMachineScheduler.cpp',
     'MediaDevices.cpp',
     'MediaManager.cpp',
     'MediaPromise.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamError.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamTrack.cpp',
     'MediaTaskQueue.cpp',
-    'MediaTimer.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',
     'MP3FrameParser.cpp',
     'RTCIdentityProviderRegistrar.cpp',
     'RtspMediaResource.cpp',
     'SharedThreadPool.cpp',
     'StreamBuffer.cpp',
     'TextTrack.cpp',