Bug 968016 - Use SharedThreadPool instead of manually managed threads for the media decoding. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Tue, 18 Feb 2014 11:53:52 +1300
changeset 169510 891419fc6190b565692053048114d7e45d8d81de
parent 169509 7102981f5c7208806bac2d697600c173fe3a6ce1
child 169511 654700db7152945698a0c3fb6411f106b912b7dc
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerskinetik
bugs968016
milestone30.0a1
Bug 968016 - Use SharedThreadPool instead of manually managed threads for the media decoding. r=kinetik
content/media/MediaDecoder.cpp
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/MediaTaskQueue.cpp
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -1477,17 +1477,17 @@ void MediaDecoder::MoveLoadsToBackground
 void MediaDecoder::UpdatePlaybackOffset(int64_t aOffset)
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mPlaybackPosition = std::max(aOffset, mPlaybackPosition);
 }
 
 bool MediaDecoder::OnStateMachineThread() const
 {
-  return IsCurrentThread(MediaDecoderStateMachine::GetStateMachineThread());
+  return mDecoderStateMachine->OnStateMachineThread();
 }
 
 void MediaDecoder::NotifyAudioAvailableListener()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mDecoderStateMachine) {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     mDecoderStateMachine->NotifyAudioAvailableListener();
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -23,17 +23,18 @@
 #include "nsDeque.h"
 #include "AudioSegment.h"
 #include "VideoSegment.h"
 #include "ImageContainer.h"
 #include "nsComponentManagerUtils.h"
 #include "nsITimer.h"
 #include "nsContentUtils.h"
 #include "MediaShutdownManager.h"
-
+#include "SharedThreadPool.h"
+#include "MediaTaskQueue.h"
 #include "prenv.h"
 #include "mozilla/Preferences.h"
 #include "gfx2DGlue.h"
 
 #include <algorithm>
 
 namespace mozilla {
 
@@ -141,36 +142,30 @@ static const int64_t ESTIMATED_DURATION_
 static TimeDuration UsecsToDuration(int64_t aUsecs) {
   return TimeDuration::FromMilliseconds(static_cast<double>(aUsecs) / USECS_PER_MS);
 }
 
 static int64_t DurationToUsecs(TimeDuration aDuration) {
   return static_cast<int64_t>(aDuration.ToSeconds() * USECS_PER_S);
 }
 
-// Owns the global state machine thread and counts of
-// state machine and decoder threads. There should
-// only be one instance of this class.
 class StateMachineTracker
 {
 private:
-  StateMachineTracker() :
-    mMonitor("media.statemachinetracker"),
-    mStateMachineCount(0),
-    mDecodeThreadCount(0),
-    mStateMachineThread(nullptr)
+  StateMachineTracker()
+    : mMonitor("media.statemachinetracker")
+    , mStateMachineCount(0)
   {
      MOZ_COUNT_CTOR(StateMachineTracker);
      NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   }
 
   ~StateMachineTracker()
   {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-
     MOZ_COUNT_DTOR(StateMachineTracker);
   }
 
 public:
   // Access singleton instance. This is initially called on the main
   // thread in the MediaDecoderStateMachine constructor resulting
   // in the global object being created lazily. Non-main thread
   // access always occurs after this and uses the monitor to
@@ -188,74 +183,34 @@ public:
   // Return the global state machine thread. Call from any thread.
   nsIThread* GetGlobalStateMachineThread()
   {
     ReentrantMonitorAutoEnter mon(mMonitor);
     NS_ASSERTION(mStateMachineThread, "Should have non-null state machine thread!");
     return mStateMachineThread->GetThread();
   }
 
-  // Requests that a decode thread be created for aStateMachine. The thread
-  // may be created immediately, or after some delay, once a thread becomes
-  // available. The request can be cancelled using CancelCreateDecodeThread().
-  // It's the callers responsibility to not call this more than once for any
-  // given state machine.
-  nsresult RequestCreateDecodeThread(MediaDecoderStateMachine* aStateMachine);
-
-  // Cancels a request made by RequestCreateDecodeThread to create a decode
-  // thread for aStateMachine.
-  nsresult CancelCreateDecodeThread(MediaDecoderStateMachine* aStateMachine);
-
-  // Maximum number of active decode threads allowed. When more
-  // than this number are active the thread creation will fail.
-  static const uint32_t MAX_DECODE_THREADS = 25;
-
-  // Returns the number of active decode threads.
-  // Call on any thread. Holds the internal monitor so don't
-  // call with any other monitor held to avoid deadlock.
-  uint32_t GetDecodeThreadCount();
-
-  // Keep track of the fact that a decode thread was destroyed.
-  // Call on any thread. Holds the internal monitor so don't
-  // call with any other monitor held to avoid deadlock.
-  void NoteDecodeThreadDestroyed();
-
-#ifdef DEBUG
-  // Returns true if aStateMachine has a pending request for a
-  // decode thread.
-  bool IsQueued(MediaDecoderStateMachine* aStateMachine);
-#endif
-
 private:
   // Holds global instance of StateMachineTracker.
   // Writable on main thread only.
   static StateMachineTracker* sInstance;
 
   // Reentrant monitor that must be obtained to access
   // the decode thread count member and methods.
   ReentrantMonitor mMonitor;
 
   // Number of instances of MediaDecoderStateMachine
   // that are currently instantiated. Access on the
   // main thread only.
   uint32_t mStateMachineCount;
 
-  // Number of instances of decoder threads that are
-  // currently instantiated. Access only with the
-  // mMonitor lock held. Can be used from any thread.
-  uint32_t mDecodeThreadCount;
-
   // Global state machine thread. Write on the main thread
   // only, read from the decoder threads. Synchronized via
   // the mMonitor.
   nsRefPtr<StateMachineThread> mStateMachineThread;
-
-  // Queue of state machines waiting for decode threads. Entries at the front
-  // get their threads first.
-  nsDeque mPending;
 };
 
 StateMachineTracker* StateMachineTracker::sInstance = nullptr;
 
 StateMachineTracker& StateMachineTracker::Instance()
 {
   if (!sInstance) {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@@ -272,115 +227,34 @@ void StateMachineTracker::EnsureGlobalSt
     NS_ASSERTION(!mStateMachineThread, "Should have null state machine thread!");
     mStateMachineThread = new StateMachineThread();
     DebugOnly<nsresult> rv = mStateMachineThread->Init();
     NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "Can't create media state machine thread");
   }
   mStateMachineCount++;
 }
 
-#ifdef DEBUG
-bool StateMachineTracker::IsQueued(MediaDecoderStateMachine* aStateMachine)
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  int32_t size = mPending.GetSize();
-  for (int i = 0; i < size; ++i) {
-    MediaDecoderStateMachine* m =
-      static_cast<MediaDecoderStateMachine*>(mPending.ObjectAt(i));
-    if (m == aStateMachine) {
-      return true;
-    }
-  }
-  return false;
-}
-#endif
-
 void StateMachineTracker::CleanupGlobalStateMachine()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ABORT_IF_FALSE(mStateMachineCount > 0,
     "State machine ref count must be > 0");
   mStateMachineCount--;
   if (mStateMachineCount == 0) {
     DECODER_LOG(PR_LOG_DEBUG, ("Destroying media state machine thread"));
-    NS_ASSERTION(mPending.GetSize() == 0, "Shouldn't all requests be handled by now?");
     {
       ReentrantMonitorAutoEnter mon(mMonitor);
       mStateMachineThread->Shutdown();
       mStateMachineThread = nullptr;
-      NS_ASSERTION(mDecodeThreadCount == 0, "Decode thread count must be zero.");
       sInstance = nullptr;
     }
     delete this;
   }
 }
 
-void StateMachineTracker::NoteDecodeThreadDestroyed()
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  --mDecodeThreadCount;
-  while (mDecodeThreadCount < MAX_DECODE_THREADS && mPending.GetSize() > 0) {
-    MediaDecoderStateMachine* m =
-      static_cast<MediaDecoderStateMachine*>(mPending.PopFront());
-    nsresult rv;
-    {
-      ReentrantMonitorAutoExit exitMon(mMonitor);
-      rv = m->StartDecodeThread();
-    }
-    if (NS_SUCCEEDED(rv)) {
-      ++mDecodeThreadCount;
-    }
-  }
-}
-
-uint32_t StateMachineTracker::GetDecodeThreadCount()
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  return mDecodeThreadCount;
-}
-
-nsresult StateMachineTracker::CancelCreateDecodeThread(MediaDecoderStateMachine* aStateMachine) {
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  int32_t size = mPending.GetSize();
-  for (int32_t i = 0; i < size; ++i) {
-    void* m = static_cast<MediaDecoderStateMachine*>(mPending.ObjectAt(i));
-    if (m == aStateMachine) {
-      mPending.RemoveObjectAt(i);
-      break;
-    }
-  }
-  NS_ASSERTION(!IsQueued(aStateMachine), "State machine should no longer have queued request.");
-  return NS_OK;
-}
-
-nsresult StateMachineTracker::RequestCreateDecodeThread(MediaDecoderStateMachine* aStateMachine)
-{
-  NS_ENSURE_STATE(aStateMachine);
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  if (mPending.GetSize() > 0 || mDecodeThreadCount + 1 >= MAX_DECODE_THREADS) {
-    // If there's already state machines in the queue, or we've exceeded the
-    // limit, append the state machine to the queue of state machines waiting
-    // for a decode thread. This ensures state machines already waiting get
-    // their threads first.
-    mPending.Push(aStateMachine);
-    return NS_OK;
-  }
-  nsresult rv;
-  {
-    ReentrantMonitorAutoExit exitMon(mMonitor);
-    rv = aStateMachine->StartDecodeThread();
-  }
-  if (NS_SUCCEEDED(rv)) {
-    ++mDecodeThreadCount;
-  }
-  NS_ASSERTION(mDecodeThreadCount <= MAX_DECODE_THREADS,
-                "Should keep to thread limit!");
-  return NS_OK;
-}
-
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
   mState(DECODER_STATE_DECODING_METADATA),
   mSyncPointInMediaStream(-1),
   mSyncPointInDecodedStream(-1),
   mResetPlayStartTime(false),
@@ -400,27 +274,26 @@ MediaDecoderStateMachine::MediaDecoderSt
   mBasePosition(0),
   mAudioCaptured(false),
   mTransportSeekable(true),
   mMediaSeekable(true),
   mPositionChangeQueued(false),
   mAudioCompleted(false),
   mGotDurationFromMetaData(false),
   mStopDecodeThread(true),
-  mDecodeThreadIdle(false),
+  mDispatchedEventToDecode(false),
   mStopAudioThread(true),
   mQuickBuffering(false),
   mIsRunning(false),
   mRunAgain(false),
   mDispatchedRunEvent(false),
   mDecodeThreadWaiting(false),
   mRealTime(aRealTime),
   mDidThrottleAudioDecoding(false),
   mDidThrottleVideoDecoding(false),
-  mRequestedNewDecodeThread(false),
   mEventManager(aDecoder),
   mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   StateMachineTracker::Instance().EnsureGlobalStateMachine();
 
@@ -458,22 +331,25 @@ MediaDecoderStateMachine::MediaDecoderSt
 }
 
 MediaDecoderStateMachine::~MediaDecoderStateMachine()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(MediaDecoderStateMachine);
   NS_ASSERTION(!mPendingWakeDecoder.get(),
                "WakeDecoder should have been revoked already");
-  NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
-    "Should not have a pending request for a new decode thread");
-  NS_ASSERTION(!mRequestedNewDecodeThread,
-    "Should not have (or flagged) a pending request for a new decode thread");
-  if (mTimer)
+
+  if (mDecodeTaskQueue) {
+    mDecodeTaskQueue->Shutdown();
+    mDecodeTaskQueue = nullptr;
+  }
+
+  if (mTimer) {
     mTimer->Cancel();
+  }
   mTimer = nullptr;
   mReader = nullptr;
 
   StateMachineTracker::Instance().CleanupGlobalStateMachine();
 #ifdef XP_WIN
   timeEndPeriod(1);
 #endif
 }
@@ -503,61 +379,55 @@ int64_t MediaDecoderStateMachine::GetDec
   if (mAudioEndTime != -1) {
     audioDecoded += mAudioEndTime - GetMediaTime();
   }
   return audioDecoded;
 }
 
 void MediaDecoderStateMachine::DecodeThreadRun()
 {
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  mReader->OnDecodeThreadStart();
-
-  {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-    if (mState == DECODER_STATE_DECODING_METADATA &&
-        NS_FAILED(DecodeMetadata())) {
-      NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                   "Should be in shutdown state if metadata loading fails.");
-      DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-    }
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-    while (mState != DECODER_STATE_SHUTDOWN &&
-           mState != DECODER_STATE_COMPLETED &&
-           mState != DECODER_STATE_DORMANT &&
-           !mStopDecodeThread)
-    {
-      if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
-        DecodeLoop();
-      } else if (mState == DECODER_STATE_SEEKING) {
-        DecodeSeek();
-      } else if (mState == DECODER_STATE_DECODING_METADATA) {
-        if (NS_FAILED(DecodeMetadata())) {
-          NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                       "Should be in shutdown state if metadata loading fails.");
-          DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-        }
-      } else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
-        mDecoder->GetReentrantMonitor().Wait();
-
-        if (!mReader->IsWaitingMediaResources()) {
-          // change state to DECODER_STATE_WAIT_FOR_RESOURCES
-          StartDecodeMetadata();
-        }
-      } else if (mState == DECODER_STATE_DORMANT) {
-        mDecoder->GetReentrantMonitor().Wait();
-      }
-    }
-
-    mDecodeThreadIdle = true;
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
+  if (mState == DECODER_STATE_DECODING_METADATA &&
+      NS_FAILED(DecodeMetadata())) {
+    NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
+                  "Should be in shutdown state if metadata loading fails.");
+    DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
   }
 
-  mReader->OnDecodeThreadFinish();
+  while (mState != DECODER_STATE_SHUTDOWN &&
+         mState != DECODER_STATE_COMPLETED &&
+         mState != DECODER_STATE_DORMANT &&
+         !mStopDecodeThread)
+  {
+    if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
+      DecodeLoop();
+    } else if (mState == DECODER_STATE_SEEKING) {
+      DecodeSeek();
+    } else if (mState == DECODER_STATE_DECODING_METADATA) {
+      if (NS_FAILED(DecodeMetadata())) {
+        NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
+                      "Should be in shutdown state if metadata loading fails.");
+        DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
+      }
+    } else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
+      mDecoder->GetReentrantMonitor().Wait();
+
+      if (!mReader->IsWaitingMediaResources()) {
+        // change state to DECODER_STATE_WAIT_FOR_RESOURCES
+        StartDecodeMetadata();
+      }
+    } else if (mState == DECODER_STATE_DORMANT) {
+      mDecoder->GetReentrantMonitor().Wait();
+    }
+  }
+
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
+  mDispatchedEventToDecode = false;
+  mon.NotifyAll();
 }
 
 void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
                                                DecodedStreamData* aStream,
                                                AudioSegment* aOutput)
 {
   NS_ASSERTION(OnDecodeThread() ||
                OnStateMachineThread(), "Should be on decode thread or state machine thread");
@@ -1307,20 +1177,31 @@ uint32_t MediaDecoderStateMachine::PlayF
   if (offset != -1) {
     mDecoder->UpdatePlaybackOffset(offset);
   }
   return frames;
 }
 
 nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<SharedThreadPool> decodePool(
+    SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"),
+                          Preferences::GetUint("media.num-decode-threads", 25)));
+  NS_ENSURE_TRUE(decodePool, NS_ERROR_FAILURE);
+
+  mDecodeTaskQueue = new MediaTaskQueue(decodePool.forget());
+  NS_ENSURE_TRUE(mDecodeTaskQueue, NS_ERROR_FAILURE);
+
   MediaDecoderReader* cloneReader = nullptr;
   if (aCloneDonor) {
     cloneReader = static_cast<MediaDecoderStateMachine*>(aCloneDonor)->mReader;
   }
+
   return mReader->Init(cloneReader);
 }
 
 void MediaDecoderStateMachine::StopPlayback()
 {
   DECODER_LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get()));
 
   AssertCurrentThreadInMonitor();
@@ -1701,40 +1582,18 @@ void MediaDecoderStateMachine::Seek(doub
   }
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::StopDecodeThread()
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   AssertCurrentThreadInMonitor();
-  if (mRequestedNewDecodeThread) {
-    // We've requested that the decode be created, but it hasn't been yet.
-    // Cancel that request.
-    NS_ASSERTION(!mDecodeThread,
-      "Shouldn't have a decode thread until after request processed");
-    StateMachineTracker::Instance().CancelCreateDecodeThread(this);
-    mRequestedNewDecodeThread = false;
-  }
   mStopDecodeThread = true;
   mDecoder->GetReentrantMonitor().NotifyAll();
-  if (mDecodeThread) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder.get()));
-    {
-      ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-      mDecodeThread->Shutdown();
-      StateMachineTracker::Instance().NoteDecodeThreadDestroyed();
-    }
-    mDecodeThread = nullptr;
-    mDecodeThreadIdle = false;
-  }
-  NS_ASSERTION(!mRequestedNewDecodeThread,
-    "Any pending requests for decode threads must be canceled and unflagged");
-  NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
-    "Any pending requests for decode threads must be canceled");
 }
 
 void MediaDecoderStateMachine::StopAudioThread()
 {
   NS_ASSERTION(OnDecodeThread() ||
                OnStateMachineThread(), "Should be on decode thread or state machine thread");
   AssertCurrentThreadInMonitor();
 
@@ -1763,74 +1622,26 @@ MediaDecoderStateMachine::ScheduleDecode
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   AssertCurrentThreadInMonitor();
 
   mStopDecodeThread = false;
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
-  if (mDecodeThread) {
-    NS_ASSERTION(!mRequestedNewDecodeThread,
-      "Shouldn't have requested new decode thread when we have a decode thread");
-    // We already have a decode thread...
-    if (mDecodeThreadIdle) {
-      // ... and it's not been shutdown yet, wake it up.
-      nsCOMPtr<nsIRunnable> event =
-        NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun);
-      mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
-      mDecodeThreadIdle = false;
-    }
-    return NS_OK;
-  } else if (!mRequestedNewDecodeThread) {
-  // We don't already have a decode thread, request a new one.
-    mRequestedNewDecodeThread = true;
-    ReentrantMonitorAutoExit mon(mDecoder->GetReentrantMonitor());
-    StateMachineTracker::Instance().RequestCreateDecodeThread(this);
+  if (!mDispatchedEventToDecode) {
+    nsresult rv = mDecodeTaskQueue->Dispatch(
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun));
+    NS_ENSURE_SUCCESS(rv, rv);
+    mDispatchedEventToDecode = true;
   }
   return NS_OK;
 }
 
 nsresult
-MediaDecoderStateMachine::StartDecodeThread()
-{
-  NS_ASSERTION(StateMachineTracker::Instance().GetDecodeThreadCount() <
-               StateMachineTracker::MAX_DECODE_THREADS,
-               "Should not have reached decode thread limit");
-
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
-    "Should not already have a pending request for a new decode thread.");
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-  NS_ASSERTION(!mDecodeThread, "Should not have decode thread yet");
-  NS_ASSERTION(mRequestedNewDecodeThread, "Should have requested this...");
-
-  mRequestedNewDecodeThread = false;
-
-  nsresult rv = NS_NewNamedThread("Media Decode",
-                                  getter_AddRefs(mDecodeThread),
-                                  nullptr,
-                                  MEDIA_THREAD_STACK_SIZE);
-  if (NS_FAILED(rv)) {
-    // Give up, report error to media element.
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-    return rv;
-  }
-
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun);
-  mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
-  mDecodeThreadIdle = false;
-
-  return NS_OK;
-}
-
-nsresult
 MediaDecoderStateMachine::StartAudioThread()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   AssertCurrentThreadInMonitor();
   if (mAudioCaptured) {
     NS_ASSERTION(mStopAudioThread, "mStopAudioThread must always be true if audio is captured");
     return NS_OK;
@@ -2201,23 +2012,27 @@ nsresult MediaDecoderStateMachine::RunSt
       // running in a nested event loop waiting for Shutdown() on
       // mAudioThread to complete.  Return to the event loop and let it
       // finish processing before continuing with shutdown.
       if (mAudioThread) {
         MOZ_ASSERT(mStopAudioThread);
         return NS_OK;
       }
       StopDecodeThread();
+
+      {
+        ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+        // Wait for the thread decoding to exit.
+        mDecodeTaskQueue->Shutdown();
+        mReader->ReleaseMediaResources();
+      }
       // Now that those threads are stopped, there's no possibility of
       // mPendingWakeDecoder being needed again. Revoke it.
       mPendingWakeDecoder = nullptr;
-      {
-        ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-        mReader->ReleaseMediaResources();
-      }
+
       NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
                    "How did we escape from the shutdown state?");
       // We must daisy-chain these events to destroy the decoder. We must
       // destroy the decoder on the main thread, but we can't destroy the
       // decoder while this thread holds the decoder monitor. We can't
       // dispatch an event to the main thread to destroy the decoder from
       // here, as the event may run before the dispatch returns, and we
       // hold the decoder monitor here. We also want to guarantee that the
@@ -2898,19 +2713,24 @@ nsresult MediaDecoderStateMachine::Sched
 
   res = mTimer->InitWithFuncCallback(mozilla::TimeoutExpired,
                                      this,
                                      ms,
                                      nsITimer::TYPE_ONE_SHOT);
   return res;
 }
 
+bool MediaDecoderStateMachine::OnDecodeThread() const
+{
+  return mDecodeTaskQueue->IsCurrentThreadIn();
+}
+
 bool MediaDecoderStateMachine::OnStateMachineThread() const
 {
-    return IsCurrentThread(GetStateMachineThread());
+  return IsCurrentThread(GetStateMachineThread());
 }
 
 nsIThread* MediaDecoderStateMachine::GetStateMachineThread()
 {
   return StateMachineTracker::Instance().GetGlobalStateMachineThread();
 }
 
 void MediaDecoderStateMachine::NotifyAudioAvailableListener()
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -86,16 +86,18 @@ hardware (via AudioStream).
 #include "MediaMetadataManager.h"
 
 class nsITimer;
 
 namespace mozilla {
 
 class AudioSegment;
 class VideoSegment;
+class MediaTaskQueue;
+class SharedThreadPool;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
 // implementation.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
@@ -171,19 +173,17 @@ public:
   // The duration is only changed if its significantly different than the
   // the current duration, as the incoming duration is an estimate and so
   // often is unstable as more data is read and the estimate is updated.
   // Can result in a durationchangeevent. aDuration is in microseconds.
   void UpdateEstimatedDuration(int64_t aDuration);
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
-  bool OnDecodeThread() const {
-    return IsCurrentThread(mDecodeThread);
-  }
+  bool OnDecodeThread() const;
   bool OnStateMachineThread() const;
   bool OnAudioThread() const {
     return IsCurrentThread(mAudioThread);
   }
 
   MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
 
   // Cause state transitions. These methods obtain the decoder monitor
@@ -309,22 +309,16 @@ public:
   // 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);
 
-  // Creates and starts a new decode thread. Don't call this directly,
-  // request a new decode thread by calling
-  // StateMachineTracker::RequestCreateDecodeThread().
-  // The decoder monitor must not be held. Called on the state machine thread.
-  nsresult StartDecodeThread();
-
   // Timer function to implement ScheduleStateMachine(aUsecs).
   void TimeoutExpired();
 
   // 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 ReleaseDecoder() {
@@ -612,18 +606,20 @@ private:
   // that interested threads can wake up and alter behaviour if appropriate
   // Accessed on state machine, audio, main, and AV thread.
   State mState;
 
   // Thread for pushing audio onto the audio hardware.
   // The "audio push thread".
   nsCOMPtr<nsIThread> mAudioThread;
 
-  // Thread for decoding video in background. The "decode thread".
-  nsCOMPtr<nsIThread> mDecodeThread;
+  // 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;
 
   // Timer to call the state machine Run() method. Used by
   // ScheduleStateMachine(). Access protected by decoder monitor.
   nsCOMPtr<nsITimer> mTimer;
 
   // Timestamp at which the next state machine Run() method will be called.
   // If this is non-null, a call to Run() is scheduled, either by a timer,
   // or via an event. Access protected by decoder monitor.
@@ -775,22 +771,20 @@ private:
   // True if mDuration has a value obtained from an HTTP header, or from
   // the media index/metadata. Accessed on the state machine thread.
   bool mGotDurationFromMetaData;
 
   // False while decode thread should be running. Accessed state machine
   // and decode threads. Syncrhonised by decoder monitor.
   bool mStopDecodeThread;
 
-  // True when the decode thread run function has finished, but the thread
-  // has not necessarily been shut down yet. This can happen if we switch
-  // from COMPLETED state to SEEKING before the state machine has a chance
-  // to run in the COMPLETED state and shutdown the decode thread.
-  // Synchronised by the decoder monitor.
-  bool mDecodeThreadIdle;
+  // True if we've dispatched an event to the decode task queue to call
+  // DecodeThreadRun(). We use this flag to prevent us from dispatching
+  // unneccessary runnables, since the decode thread runs in a loop.
+  bool mDispatchedEventToDecode;
 
   // False while audio thread should be running. Accessed state machine
   // and audio threads. Syncrhonised by decoder monitor.
   bool mStopAudioThread;
 
   // If this is true while we're in buffering mode, we can exit early,
   // as it's likely we may be able to playback. This happens when we enter
   // buffering mode soon after the decode starts, because the decode-ahead
@@ -822,20 +816,16 @@ private:
   bool mRealTime;
 
   // Record whether audio and video decoding were throttled during the
   // previous iteration of DecodeLooop. When we transition from
   // throttled to not-throttled we need to pump decoding.
   bool mDidThrottleAudioDecoding;
   bool mDidThrottleVideoDecoding;
 
-  // True if we've requested a new decode thread, but it has not yet been
-  // created. Synchronized by the decoder monitor.
-  bool mRequestedNewDecodeThread;
-
   // Manager for queuing and dispatching MozAudioAvailable events.  The
   // event manager is accessed from the state machine and audio threads,
   // and takes care of synchronizing access to its internal queue.
   AudioAvailableEventManager mEventManager;
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
--- a/content/media/MediaTaskQueue.cpp
+++ b/content/media/MediaTaskQueue.cpp
@@ -121,16 +121,23 @@ MediaTaskQueue::Runner::Run()
 
   // Note that dropping the queue monitor before running the task, and
   // taking the monitor again after the task has run ensures we have memory
   // fences enforced. This means that if the object we're calling wasn't
   // designed to be threadsafe, it will be, provided we're only calling it
   // in this task queue.
   event->Run();
 
+  // Drop the reference to event. The event will hold a reference to the
+  // object it's calling, and we don't want to keep it alive, it may be
+  // making assumptions what holds references to it. This is especially
+  // the case if the object is waiting for us to shutdown, so that it
+  // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
+  event = nullptr;
+
   {
     MonitorAutoLock mon(mQueue->mQueueMonitor);
     if (mQueue->mTasks.size() == 0) {
       // No more events to run. Exit the task runner.
       mQueue->mIsRunning = false;
       mon.NotifyAll();
       mQueue->mRunningThread = nullptr;
       return NS_OK;