Bug 592833 - Backout due to suspected android crashtest permaorange. r=philor
authorChris Pearce <chris@pearce.org.nz>
Wed, 06 Jul 2011 19:21:49 +1200
changeset 72411 e95d8bef125fc3525168cb1b160bb552bac611e2
parent 72401 04c5bfe94282e22cddd72b2a6f673225dc68af0d
child 72412 fcf2cf152e13db24162e95cd1ac0a1c64cd977e1
push idunknown
push userunknown
push dateunknown
reviewersphilor
bugs592833
milestone8.0a1
Bug 592833 - Backout due to suspected android crashtest permaorange. r=philor
content/html/content/src/nsHTMLMediaElement.cpp
content/media/nsBuiltinDecoder.cpp
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderReader.cpp
content/media/nsBuiltinDecoderReader.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/nsMediaDecoder.h
content/media/ogg/nsOggReader.cpp
content/media/ogg/nsOggReader.h
content/media/test/manifest.js
content/media/test/seek1.js
content/media/test/test_buffered.html
content/media/test/test_bug493187.html
content/media/wave/nsWaveReader.cpp
content/media/webm/nsWebMReader.cpp
content/media/webm/nsWebMReader.h
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1982,21 +1982,21 @@ void nsHTMLMediaElement::ResourceLoaded(
 
 void nsHTMLMediaElement::NetworkError()
 {
   Error(nsIDOMMediaError::MEDIA_ERR_NETWORK);
 }
 
 void nsHTMLMediaElement::DecodeError()
 {
-  if (mDecoder) {
-    mDecoder->Shutdown();
-    mDecoder = nsnull;
-  }
   if (mIsLoadingFromSourceChildren) {
+    if (mDecoder) {
+      mDecoder->Shutdown();
+      mDecoder = nsnull;
+    }
     mError = nsnull;
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
       NS_WARNING("Should know the source we were loading from!");
     }
   } else {
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -41,17 +41,16 @@
 #include "nsNetUtil.h"
 #include "nsAudioStream.h"
 #include "nsHTMLVideoElement.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsTArray.h"
 #include "VideoUtils.h"
 #include "nsBuiltinDecoder.h"
-#include "nsBuiltinDecoderStateMachine.h"
 
 using namespace mozilla;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gBuiltinDecoderLog;
 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
 #else
 #define LOG(type, msg)
@@ -118,16 +117,30 @@ PRBool nsBuiltinDecoder::Init(nsHTMLMedi
   if (!nsMediaDecoder::Init(aElement))
     return PR_FALSE;
 
   nsContentUtils::RegisterShutdownObserver(this);
   mImageContainer = aElement->GetImageContainer();
   return PR_TRUE;
 }
 
+void nsBuiltinDecoder::Stop()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread");
+
+  // The decode thread must die before the state machine can die.
+  // The state machine must die before the reader.
+  // The state machine must die before the decoder.
+  if (mStateMachineThread)
+    mStateMachineThread->Shutdown();
+
+  mStateMachineThread = nsnull;
+  mDecoderStateMachine = nsnull;
+}
+
 void nsBuiltinDecoder::Shutdown()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   
   if (mShuttingDown)
     return;
 
   mShuttingDown = PR_TRUE;
@@ -143,29 +156,40 @@ void nsBuiltinDecoder::Shutdown()
   // to prevent shutdown from deadlocking.
   if (mStream) {
     mStream->Close();
   }
 
   ChangeState(PLAY_STATE_SHUTDOWN);
   nsMediaDecoder::Shutdown();
 
+  // We can't destroy mDecoderStateMachine until mStateMachineThread is shut down.
+  // It's unsafe to Shutdown() the decode thread here, as
+  // nsIThread::Shutdown() may run events, such as JS event handlers,
+  // and we could be running at an unsafe time such as during element
+  // destruction.
+  // So we destroy the decoder on the main thread in an asynchronous event.
+  // See bug 468721.
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(this, &nsBuiltinDecoder::Stop);
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+
   nsContentUtils::UnregisterShutdownObserver(this);
 }
 
 nsBuiltinDecoder::~nsBuiltinDecoder()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   UnpinForSeek();
   MOZ_COUNT_DTOR(nsBuiltinDecoder);
 }
 
 nsresult nsBuiltinDecoder::Load(nsMediaStream* aStream,
-                                nsIStreamListener** aStreamListener,
-                                nsMediaDecoder* aCloneDonor)
+                            nsIStreamListener** aStreamListener,
+                            nsMediaDecoder* aCloneDonor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
   {
     // Hold the lock while we do this to set proper lock ordering
@@ -200,50 +224,48 @@ nsresult nsBuiltinDecoder::Load(nsMediaS
     if (mFrameBufferLength > 0) {
       // The valid mFrameBufferLength value was specified earlier
       mDecoderStateMachine->SetFrameBufferLength(mFrameBufferLength);
     }
   }
 
   ChangeState(PLAY_STATE_LOADING);
 
-  return ScheduleStateMachineThread();
+  return StartStateMachineThread();
 }
 
 nsresult nsBuiltinDecoder::RequestFrameBufferLength(PRUint32 aLength)
 {
   nsresult res = nsMediaDecoder::RequestFrameBufferLength(aLength);
   NS_ENSURE_SUCCESS(res,res);
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   if (mDecoderStateMachine) {
       mDecoderStateMachine->SetFrameBufferLength(aLength);
   }
   return res;
 }
 
-nsresult nsBuiltinDecoder::ScheduleStateMachineThread()
+nsresult nsBuiltinDecoder::StartStateMachineThread()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(mDecoderStateMachine,
                "Must have state machine to start state machine thread");
-
-  if (mShuttingDown)
+  if (mStateMachineThread) {
     return NS_OK;
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  nsBuiltinDecoderStateMachine* m =
-    static_cast<nsBuiltinDecoderStateMachine*>(mDecoderStateMachine.get());
-  return m->ScheduleStateMachine();
+  }
+  nsresult rv = NS_NewThread(getter_AddRefs(mStateMachineThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return mStateMachineThread->Dispatch(mDecoderStateMachine, NS_DISPATCH_NORMAL);
 }
 
 nsresult nsBuiltinDecoder::Play()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  nsresult res = ScheduleStateMachineThread();
+  nsresult res = StartStateMachineThread();
   NS_ENSURE_SUCCESS(res,res);
   if (mPlayState == PLAY_STATE_SEEKING) {
     mNextState = PLAY_STATE_PLAYING;
     return NS_OK;
   }
   if (mPlayState == PLAY_STATE_ENDED)
     return Seek(0);
 
@@ -271,17 +293,17 @@ nsresult nsBuiltinDecoder::Seek(double a
     }
     else {
       mNextState = mPlayState;
     }
     PinForSeek();
     ChangeState(PLAY_STATE_SEEKING);
   }
 
-  return ScheduleStateMachineThread();
+  return StartStateMachineThread();
 }
 
 nsresult nsBuiltinDecoder::PlaybackRateChanged()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -505,30 +527,30 @@ nsBuiltinDecoder::GetStatistics()
   }
 
   return result;
 }
 
 double nsBuiltinDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
 {
   GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
+  NS_ASSERTION(NS_IsMainThread() || IsCurrentThread(mStateMachineThread),
                "Should be on main or state machine thread.");
 
   PRInt64 length = mStream ? mStream->GetLength() : -1;
   if (mDuration >= 0 && length >= 0) {
     *aReliable = PR_TRUE;
     return length * static_cast<double>(USECS_PER_S) / mDuration;
   }
   return mPlaybackStatistics.GetRateAtLastStop(aReliable);
 }
 
 void nsBuiltinDecoder::UpdatePlaybackRate()
 {
-  NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
+  NS_ASSERTION(NS_IsMainThread() || IsCurrentThread(mStateMachineThread),
                "Should be on main or state machine thread.");
   GetReentrantMonitor().AssertCurrentThreadIn();
   if (!mStream)
     return;
   PRPackedBool reliable;
   PRUint32 rate = PRUint32(ComputePlaybackRate(&reliable));
   if (reliable) {
     // Avoid passing a zero rate
@@ -844,30 +866,26 @@ void nsBuiltinDecoder::Resume(PRBool aFo
   if (aForceBuffering) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mDecoderStateMachine->StartBuffering();
   }
 }
 
 void nsBuiltinDecoder::StopProgressUpdates()
 {
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
-  GetReentrantMonitor().AssertCurrentThreadIn();
+  NS_ASSERTION(IsCurrentThread(mStateMachineThread), "Should be on state machine thread.");
   mIgnoreProgressData = PR_TRUE;
   if (mStream) {
     mStream->SetReadMode(nsMediaCacheStream::MODE_METADATA);
   }
 }
 
 void nsBuiltinDecoder::StartProgressUpdates()
 {
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
-  GetReentrantMonitor().AssertCurrentThreadIn();
+  NS_ASSERTION(IsCurrentThread(mStateMachineThread), "Should be on state machine thread.");
   mIgnoreProgressData = PR_FALSE;
   if (mStream) {
     mStream->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
     mDecoderPosition = mPlaybackPosition = mStream->Tell();
   }
 }
 
 void nsBuiltinDecoder::MoveLoadsToBackground()
@@ -878,12 +896,8 @@ void nsBuiltinDecoder::MoveLoadsToBackgr
   }
 }
 
 void nsBuiltinDecoder::UpdatePlaybackOffset(PRInt64 aOffset)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   mPlaybackPosition = NS_MAX(aOffset, mPlaybackPosition);
 }
-
-PRBool nsBuiltinDecoder::OnStateMachineThread() const {
-  return IsCurrentThread(nsBuiltinDecoderStateMachine::GetStateMachineThread());
-}
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -32,38 +32,34 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 /*
-Each video element based on nsBuiltinDecoder has a state machine to manage
-its play state and keep the current frame up to date. All state machines
-share time in a single shared thread. Each decoder also has one thread
-dedicated to decoding audio and video data. This thread is shutdown when
-playback is paused. Each decoder also has a thread to push decoded audio
-to the hardware. This thread is not created until playback starts, but
-currently is not destroyed when paused, only when playback ends.
+Each video element based on nsBuiltinDecoder has at least one thread
+dedicated to decoding video.
+
+This thread (called the state machine thread owns the resources for
+downloading and reading the media file. nsDecoderStateMachine is the
+class that needs to be implemented and it gets run on the state
+machine thread.
 
-The decoder owns the resources for downloading the media file, and the
-high level state. It holds an owning reference to the state machine
-(a subclass of nsDecoderStateMachine; nsBuiltinDecoderStateMachine) that
-owns all the resources related to decoding data, and manages the low level
-decoding operations and A/V sync. 
+The state machine thread has one event that is dispatched to it (the
+implementation of nsDecoderStateMachine) and that event runs for the
+lifetime of the playback of the resource. State shared between threads
+is synchronised with the main thread via a monitor held by the
+nsBuiltinDecoder object.
 
-Each state machine runs on the shared state machine thread. Every time some
-action is required for a state machine, it is scheduled to run on the shared
-the state machine thread. The state machine runs one "cycle" on the state
-machine thread, and then returns. If necessary, it will schedule itself to
-run again in future. While running this cycle, it must not block the
-thread, as other state machines' events may need to run. State shared
-between a state machine's threads is synchronised via the monitor owned
-by its nsBuiltinDecoder object.
+The state machine thread event consist of a Run method which is an
+infinite loop that performs the decoding operation and checks the
+state that the state machine is in and processes operations on that
+state.
 
 The Main thread controls the decode state machine by setting the value
 of a mPlayState variable and notifying on the monitor based on the
 high level player actions required (Seek, Pause, Play, etc).
 
 The player states are the states requested by the client through the
 DOM API.  They represent the desired state of the player, while the
 decoder's state represents the actual state of the decoder.
@@ -84,47 +80,35 @@ SEEKING
 COMPLETED
   Playback has completed.
 SHUTDOWN
   The decoder is about to be destroyed.
 
 State transition occurs when the Media Element calls the Play, Seek,
 etc methods on the nsBuiltinDecoder object. When the transition
 occurs nsBuiltinDecoder then calls the methods on the decoder state
-machine object to cause it to behave as required by the play state.
-State transitions will likely schedule the state machine to run to
-affect the change.
+machine object to cause it to behave appropriate to the play state.
 
 An implementation of the nsDecoderStateMachine class is the event
-that gets dispatched to the state machine thread. Each time the event is run,
-the state machine must cycle the state machine once, and then return.
-
-The state machine has the following states:
+that gets dispatched to the state machine thread. It has the following states:
 
 DECODING_METADATA
   The media headers are being loaded, and things like framerate, etc are
   being determined, and the first frame of audio/video data is being decoded.
 DECODING
-  The decode has started. If the PlayState is PLAYING, the decode thread
-  should be alive and decoding video and audio frame, the audio thread
-  should be playing audio, and the state machine should run periodically
-  to update the video frames being displayed.
+  The decode and audio threads are started and video frames displayed at
+  the required time. 
 SEEKING
-  A seek operation is in progress. The decode thread should be seeking.
+  A seek operation is in progress.
 BUFFERING
-  Decoding is paused while data is buffered for smooth playback. If playback
-  is paused (PlayState transitions to PAUSED) we'll destory the decode thread.
+  Decoding is paused while data is buffered for smooth playback.
 COMPLETED
-  The resource has completed decoding, but possibly not finished playback.
-  The decode thread will be destroyed. Once playback finished, the audio
-  thread will also be destroyed.
+  The resource has completed decoding, but not finished playback. 
 SHUTDOWN
-  The decoder object and its state machine are about to be destroyed.
-  Once the last state machine has been destroyed, the shared state machine
-  thread will also be destroyed. It will be recreated later if needed.
+  The decoder object is about to be destroyed.
 
 The following result in state transitions.
 
 Shutdown()
   Clean up any resources the nsDecoderStateMachine owns.
 Play()
   Start decoding and playback of media data.
 Buffer
@@ -168,51 +152,51 @@ player PLAYING   decoder DECODING, BUFFE
 player PAUSED    decoder DECODING, BUFFERING, SEEKING, COMPLETED
 player SEEKING   decoder SEEKING
 player COMPLETED decoder SHUTDOWN
 player SHUTDOWN  decoder SHUTDOWN
 
 The general sequence of events is:
 
 1) The video element calls Load on nsMediaDecoder. This creates the
-   state machine and starts the channel for downloading the
-   file. It instantiates and schedules the nsDecoderStateMachine. The
+   state machine thread and starts the channel for downloading the
+   file. It instantiates and starts the nsDecoderStateMachine. The
    high level LOADING state is entered, which results in the decode
-   thread being created and starting to decode metadata. These are
-   the headers that give the video size, framerate, etc. Load() returns
-   immediately to the calling video element.
+   state machine to start decoding metadata. These are the headers
+   that give the video size, framerate, etc.  It returns immediately
+   to the calling video element.
 
-2) When the metadata has been loaded by the decode thread, the state machine
-   will call a method on the video element object to inform it that this
-   step is done, so it can do the things required by the video specification
-   at this stage. The decode thread then continues to decode the first frame
+2) When the metadata has been loaded by the decode thread it will call
+   a method on the video element object to inform it that this step is
+   done, so it can do the things required by the video specification
+   at this stage. The decoder then continues to decode the first frame
    of data.
 
-3) When the first frame of data has been successfully decoded the state
-   machine calls a method on the video element object to inform it that
-   this step has been done, once again so it can do the required things
-   by the video specification at this stage.
+3) When the first frame of data has been successfully decoded it calls
+   a method on the video element object to inform it that this step
+   has been done, once again so it can do the required things by the
+   video specification at this stage.
 
    This results in the high level state changing to PLAYING or PAUSED
    depending on any user action that may have occurred.
 
-   While the play state is PLAYING, the decode thread will decode
-   data, and the audio thread will push audio data to the hardware to
-   be played. The state machine will run periodically on the shared
-   state machine thread to ensure video frames are played at the 
-   correct time; i.e. the state machine manages A/V sync.
+   The decode thread plays audio and video, if the correct frame time
+   comes around and the decoder play state is PLAYING.
+   
+a/v synchronisation is handled by the nsDecoderStateMachine implementation.
 
-The Shutdown method on nsBuiltinDecoder closes the download channel, and
-signals to the state machine that it should shutdown. The state machine
-shuts down asynchronously, and will release the owning reference to the
-state machine once its threads are shutdown.
+The Shutdown method on nsBuiltinDecoder can spin the event loop as it
+waits for threads to complete. Spinning the event loop is a bad thing
+to happen during certain times like destruction of the media
+element. To work around this the Shutdown method does nothing but
+queue an event to the main thread to perform the actual Shutdown. This
+way the shutdown can occur at a safe time.
 
-The owning object of a nsBuiltinDecoder object *MUST* call Shutdown when
-destroying the nsBuiltinDecoder object.
-
+This means the owning object of a nsBuiltinDecoder object *MUST* call
+Shutdown when destroying the nsBuiltinDecoder object.  
 */
 #if !defined(nsBuiltinDecoder_h_)
 #define nsBuiltinDecoder_h_
 
 #include "nsMediaDecoder.h"
 
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
@@ -280,19 +264,16 @@ public:
   // resource. The decoder monitor must be obtained before calling this.
   // aEndTime is in microseconds.
   virtual void SetEndTime(PRInt64 aEndTime) = 0;
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   virtual PRBool OnDecodeThread() const = 0;
 
-  // Returns PR_TRUE if the current thread is the state machine thread.
-  virtual PRBool OnStateMachineThread() const = 0;
-
   virtual nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus() = 0;
 
   // 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.
   virtual void Play() = 0;
 
   // Seeks to aTime in seconds
@@ -439,23 +420,29 @@ class nsBuiltinDecoder : public nsMediaD
   // Resume any media downloads that have been suspended. Called by the
   // media element when it is restored from the bfcache. Call on the
   // main thread only.
   virtual void Resume(PRBool aForceBuffering);
 
   // Tells our nsMediaStream to put all loads in the background.
   virtual void MoveLoadsToBackground();
 
+  // Stop the state machine thread and drop references to the thread and
+  // state machine.
+  void Stop();
+
   void AudioAvailable(float* aFrameBuffer, PRUint32 aFrameBufferLength, float aTime);
 
   // Called by the state machine to notify the decoder that the duration
   // has changed.
   void DurationChanged();
 
-  PRBool OnStateMachineThread() const;
+  PRBool OnStateMachineThread() {
+    return IsCurrentThread(mStateMachineThread);
+  }
 
   PRBool OnDecodeThread() const {
     return mDecoderStateMachine->OnDecodeThread();
   }
 
   // Returns the monitor for other threads to synchronise access to
   // state.
   ReentrantMonitor& GetReentrantMonitor() { 
@@ -575,19 +562,19 @@ class nsBuiltinDecoder : public nsMediaD
   // Return the current decode state. The decoder monitor must be
   // obtained before calling this.
   nsDecoderStateMachine::State GetDecodeState() { return mDecoderStateMachine->GetState(); }
 
 public:
   // Notifies the element that decoding has failed.
   void DecodeError();
 
-  // Schedules the state machine to run one cycle on the shared state
-  // machine thread. Main thread only.
-  nsresult ScheduleStateMachineThread();
+  // Ensures the state machine thread is running, starting a new one
+  // if necessary.
+  nsresult StartStateMachineThread();
 
   /******
    * The following members should be accessed with the decoder lock held.
    ******/
 
   // Current decoding position in the stream. This is where the decoder
   // is up to consuming the stream. This is not adjusted during decoder
   // seek operations, but it's updated at the end when we start playing
@@ -598,16 +585,19 @@ public:
   // during decoder seek operations, but it's updated at the end when we
   // start playing back again.
   PRInt64 mPlaybackPosition;
   // Data needed to estimate playback data rate. The timeline used for
   // this estimate is "decode time" (where the "current time" is the
   // time of the last decoded video frame).
   nsChannelStatistics mPlaybackStatistics;
 
+  // Thread to manage playback state machine.
+  nsCOMPtr<nsIThread> mStateMachineThread;
+
   // The current playback position of the media resource in units of
   // seconds. This is updated approximately at the framerate of the
   // video (if it is a video) or the callback period of the audio.
   // It is read and written from the main thread only.
   double mCurrentTime;
 
   // Volume that playback should start at.  0.0 = muted. 1.0 = full
   // volume.  Readable/Writeable from the main thread.
--- a/content/media/nsBuiltinDecoderReader.cpp
+++ b/content/media/nsBuiltinDecoderReader.cpp
@@ -185,17 +185,18 @@ VideoData* VideoData::Create(nsVideoInfo
   data.mPicSize = gfxIntSize(aPicture.width, aPicture.height);
   data.mStereoMode = aInfo.mStereoMode;
 
   videoImage->SetData(data); // Copies buffer
   return v.forget();
 }
 
 nsBuiltinDecoderReader::nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder)
-  : mDecoder(aDecoder)
+  : mReentrantMonitor("media.decoderreader"),
+    mDecoder(aDecoder)
 {
   MOZ_COUNT_CTOR(nsBuiltinDecoderReader);
 }
 
 nsBuiltinDecoderReader::~nsBuiltinDecoderReader()
 {
   ResetDecode();
   MOZ_COUNT_DTOR(nsBuiltinDecoderReader);
@@ -208,18 +209,17 @@ nsresult nsBuiltinDecoderReader::ResetDe
   mVideoQueue.Reset();
   mAudioQueue.Reset();
 
   return res;
 }
 
 VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64& aOutStartTime)
 {
-  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
-               "Should be on state machine or decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
 
   // Extract the start times of the bitstreams in order to calculate
   // the duration.
   PRInt64 videoStartTime = PR_INT64_MAX;
   PRInt64 audioStartTime = PR_INT64_MAX;
   VideoData* videoData = nsnull;
 
   if (HasVideo()) {
@@ -269,16 +269,17 @@ nsresult nsBuiltinDecoderReader::DecodeT
   if (HasVideo()) {
     PRBool eof = PR_FALSE;
     PRInt64 startTime = -1;
     while (HasVideo() && !eof) {
       while (mVideoQueue.GetSize() == 0 && !eof) {
         PRBool skip = PR_FALSE;
         eof = !DecodeVideoFrame(skip, 0);
         {
+          ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
           ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
           if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
             return NS_ERROR_FAILURE;
           }
         }
       }
       if (mVideoQueue.GetSize() == 0) {
         break;
@@ -293,31 +294,33 @@ nsresult nsBuiltinDecoderReader::DecodeT
         mVideoQueue.PopFront();
         video = nsnull;
       } else {
         video.forget();
         break;
       }
     }
     {
+      ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
         return NS_ERROR_FAILURE;
       }
     }
     LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime));
   }
 
   if (HasAudio()) {
     // Decode audio forward to the seek target.
     PRBool eof = PR_FALSE;
     while (HasAudio() && !eof) {
       while (!eof && mAudioQueue.GetSize() == 0) {
         eof = !DecodeAudioData();
         {
+          ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
           ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
           if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
             return NS_ERROR_FAILURE;
           }
         }
       }
       nsAutoPtr<SoundData> audio(mAudioQueue.PeekFront());
       if (audio && audio->mTime + audio->mDuration <= aTarget) {
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -393,20 +393,22 @@ template <class T> class MediaQueue : pr
 private:
   ReentrantMonitor mReentrantMonitor;
 
   // PR_TRUE when we've decoded the last frame of data in the
   // bitstream for which we're queueing sample-data.
   PRBool mEndOfStream;
 };
 
-// Encapsulates the decoding and reading of media data. Reading can only be
-// done on the decode thread thread. Never hold the decoder monitor when
-// calling into this class. Unless otherwise specified, methods and fields of
-// this class can only be accessed on the decode thread.
+// Encapsulates the decoding and reading of media data. Reading can be done
+// on either the state machine thread (when loading and seeking) or on
+// the reader thread (when it's reading and decoding). The reader encapsulates
+// the reading state and maintains it's own monitor to ensure thread safety
+// and correctness. Never hold the nsBuiltinDecoder's monitor when calling into
+// this class.
 class nsBuiltinDecoderReader : public nsRunnable {
 public:
   typedef mozilla::ReentrantMonitor ReentrantMonitor;
   typedef mozilla::ReentrantMonitorAutoEnter ReentrantMonitorAutoEnter;
 
   nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder);
   ~nsBuiltinDecoderReader();
 
@@ -445,22 +447,20 @@ public:
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
   // denote the start and end times of the media in usecs, and aCurrentTime
   // is the current playback position in microseconds.
   virtual nsresult Seek(PRInt64 aTime,
                         PRInt64 aStartTime,
                         PRInt64 aEndTime,
                         PRInt64 aCurrentTime) = 0;
 
-  // Queue of audio samples. This queue is threadsafe, and is accessed from
-  // the audio, decoder, state machine, and main threads.
+  // Queue of audio samples. This queue is threadsafe.
   MediaQueue<SoundData> mAudioQueue;
 
-  // Queue of video samples. This queue is threadsafe, and is accessed from
-  // the decoder, state machine, and main threads.
+  // Queue of video samples. This queue is threadsafe.
   MediaQueue<VideoData> mVideoQueue;
 
   // Populates aBuffered with the time ranges which are buffered. aStartTime
   // must be the presentation time of the first sample/frame in the media, e.g.
   // the media time corresponding to playback time/position 0. This function
   // should only be called on the main thread.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered,
                                PRInt64 aStartTime) = 0;
@@ -487,16 +487,22 @@ protected:
 
   // Wrapper so that DecodeVideoFrame(PRBool&,PRInt64) can be called from
   // DecodeToFirstData().
   PRBool DecodeVideoFrame() {
     PRBool f = PR_FALSE;
     return DecodeVideoFrame(f, 0);
   }
 
-  // Reference to the owning decoder object.
+  // The lock which we hold whenever we read or decode. This ensures the thread
+  // safety of the reader and its data fields.
+  ReentrantMonitor mReentrantMonitor;
+
+  // Reference to the owning decoder object. Do not hold the
+  // reader's monitor when accessing this.
   nsBuiltinDecoder* mDecoder;
 
-  // Stores presentation info required for playback.
+  // Stores presentation info required for playback. The reader's monitor
+  // must be held when accessing this.
   nsVideoInfo mInfo;
 };
 
 #endif
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -166,96 +166,46 @@ public:
     mDecoder->MetadataLoaded(mChannels, mRate);
     return NS_OK;
   }
 
   const PRUint32 mChannels;
   const PRUint32 mRate;
 };
 
-static PRUint32 gStateMachineCount = 0;
-static nsIThread* gStateMachineThread = 0;
-
-nsIThread* nsBuiltinDecoderStateMachine::GetStateMachineThread() {
-  return gStateMachineThread;
-}
-
-// Shuts down a thread asynchronously.
-class ShutdownThreadEvent : public nsRunnable 
-{
-public:
-  ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
-  ~ShutdownThreadEvent() {}
-  NS_IMETHOD Run() {
-    mThread->Shutdown();
-    mThread = nsnull;
-    return NS_OK;
-  }
-private:
-  nsCOMPtr<nsIThread> mThread;
-};
-
 nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder,
                                                            nsBuiltinDecoderReader* aReader) :
   mDecoder(aDecoder),
   mState(DECODER_STATE_DECODING_METADATA),
+  mAudioReentrantMonitor("media.audiostream"),
   mCbCrSize(0),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mSeekTime(0),
   mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mVolume(1.0),
   mSeekable(PR_TRUE),
   mPositionChangeQueued(PR_FALSE),
   mAudioCompleted(PR_FALSE),
   mGotDurationFromMetaData(PR_FALSE),
-  mStopDecodeThread(PR_TRUE),
-  mDecodeThreadIdle(PR_FALSE),
-  mStopAudioThread(PR_TRUE),
+  mStopDecodeThreads(PR_TRUE),
   mQuickBuffering(PR_FALSE),
-  mEventManager(aDecoder),
-  mIsRunning(PR_FALSE),
-  mRunAgain(PR_FALSE),
-  mDispatchedRunEvent(PR_FALSE),
-  mDecodeThreadWaiting(PR_FALSE)
+  mEventManager(aDecoder)
 {
   MOZ_COUNT_CTOR(nsBuiltinDecoderStateMachine);
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  if (gStateMachineCount == 0) {
-    NS_ASSERTION(!gStateMachineThread, "Should have null state machine thread!");
-    nsresult res = NS_NewThread(&gStateMachineThread);
-    NS_ABORT_IF_FALSE(NS_SUCCEEDED(res), "Can't create media state machine thread");
-  }
-  gStateMachineCount++;
 }
 
 nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
-  if (mTimer)
-    mTimer->Cancel();
-  mTimer = nsnull;
-  
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  NS_ABORT_IF_FALSE(gStateMachineCount > 0,
-    "State machine ref count must be > 0");
-  gStateMachineCount--;
-  if (gStateMachineCount == 0) {
-    LOG(PR_LOG_DEBUG, ("Destroying media state machine thread"));
-    nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(gStateMachineThread);
-    NS_RELEASE(gStateMachineThread);
-    gStateMachineThread = nsnull;
-    NS_DispatchToMainThread(event);
-  }
 }
 
 PRBool nsBuiltinDecoderStateMachine::HasFutureAudio() const {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
   // We've got audio ready to play if:
   // 1. We've not completed playback of audio, and
   // 2. we either have more than the threshold of decoded audio available, or
@@ -276,49 +226,18 @@ PRInt64 nsBuiltinDecoderStateMachine::Ge
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   PRInt64 audioDecoded = mReader->mAudioQueue.Duration();
   if (mAudioEndTime != -1) {
     audioDecoded += mAudioEndTime - GetMediaTime();
   }
   return audioDecoded;
 }
 
-void nsBuiltinDecoderStateMachine::DecodeThreadRun()
-{
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  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.");
-      LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-    }
-  }
-
-  while (mState != DECODER_STATE_SHUTDOWN &&
-         mState != DECODER_STATE_COMPLETED &&
-         !mStopDecodeThread)
-  {
-    if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
-      DecodeLoop();
-    } else if (mState == DECODER_STATE_SEEKING) {
-      DecodeSeek();
-    }
-  }
-
-  mDecodeThreadIdle = PR_TRUE;
-  LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
-}
-
 void nsBuiltinDecoderStateMachine::DecodeLoop()
 {
-  LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get()));
-
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
   // We want to "pump" the decode until we've got a few frames/samples decoded
   // before we consider whether decode is falling behind.
   PRBool audioPump = PR_TRUE;
   PRBool videoPump = PR_TRUE;
 
   // If the video decode is falling behind the audio, we'll start dropping the
@@ -343,21 +262,24 @@ void nsBuiltinDecoderStateMachine::Decod
   // Our local ample audio threshold. If we increase lowAudioThreshold, we'll
   // also increase this too appropriately (we don't want lowAudioThreshold to
   // be greater than ampleAudioThreshold, else we'd stop decoding!).
   PRInt64 ampleAudioThreshold = AMPLE_AUDIO_USECS;
 
   MediaQueue<VideoData>& videoQueue = mReader->mVideoQueue;
   MediaQueue<SoundData>& audioQueue = mReader->mAudioQueue;
 
-  // Main decode loop.
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
   PRBool videoPlaying = HasVideo();
   PRBool audioPlaying = HasAudio();
-  while ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
-         !mStopDecodeThread &&
+
+  // Main decode loop.
+  while (mState != DECODER_STATE_SHUTDOWN &&
+         !mStopDecodeThreads &&
          (videoPlaying || audioPlaying))
   {
     // We don't want to consider skipping to the next keyframe if we've
     // only just started up the decode loop, so wait until we've decoded
     // some frames before enabling the keyframe skip logic on video.
     if (videoPump &&
         static_cast<PRUint32>(videoQueue.GetSize()) >= videoPumpThreshold)
     {
@@ -383,17 +305,17 @@ void nsBuiltinDecoderStateMachine::Decod
         ((!audioPump && audioPlaying && GetDecodedAudioDuration() < lowAudioThreshold) ||
          (!videoPump &&
            videoPlaying &&
            static_cast<PRUint32>(videoQueue.GetSize()) < LOW_VIDEO_FRAMES)) &&
         !HasLowUndecodedData())
 
     {
       skipToNextKeyframe = PR_TRUE;
-      LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
+      LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder));
     }
 
     // Video decode.
     if (videoPlaying &&
         static_cast<PRUint32>(videoQueue.GetSize()) < AMPLE_VIDEO_FRAMES)
     {
       // Time the video decode, so that if it's slow, we can increase our low
       // audio threshold to reduce the chance of an audio underrun while we're
@@ -430,139 +352,121 @@ void nsBuiltinDecoderStateMachine::Decod
     // Notify to ensure that the AudioLoop() is not waiting, in case it was
     // waiting for more audio to be decoded.
     mDecoder->GetReentrantMonitor().NotifyAll();
 
     // The ready state can change when we've decoded data, so update the
     // ready state, so that DOM events can fire.
     UpdateReadyState();
 
-    if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
-        !mStopDecodeThread &&
+    if (mState != DECODER_STATE_SHUTDOWN &&
+        !mStopDecodeThreads &&
         (videoPlaying || audioPlaying) &&
         (!audioPlaying || (GetDecodedAudioDuration() >= ampleAudioThreshold &&
                            audioQueue.GetSize() > 0))
         &&
         (!videoPlaying ||
           static_cast<PRUint32>(videoQueue.GetSize()) >= AMPLE_VIDEO_FRAMES))
     {
       // All active bitstreams' decode is well ahead of the playback
       // position, we may as well wait for the playback to catch up. Note the
       // audio push thread acquires and notifies the decoder monitor every time
       // it pops SoundData off the audio queue. So if the audio push thread pops
       // the last SoundData off the audio queue right after that queue reported
       // it was non-empty here, we'll receive a notification on the decoder
       // monitor which will wake us up shortly after we sleep, thus preventing
       // both the decode and audio push threads waiting at the same time.
       // See bug 620326.
-      mDecodeThreadWaiting = PR_TRUE;
-      if (mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING) {
-        // We're not playing, and the decode is about to wait. This means
-        // the decode thread may not be needed in future. Signal the state
-        // machine thread to run, so it can decide whether to shutdown the
-        // decode thread.
-        ScheduleStateMachine();
-      }
-      mDecoder->GetReentrantMonitor().Wait();
-      mDecodeThreadWaiting = PR_FALSE;
+      mon.Wait();
     }
 
   } // End decode loop.
 
-  if (!mStopDecodeThread &&
+  if (!mStopDecodeThreads &&
       mState != DECODER_STATE_SHUTDOWN &&
       mState != DECODER_STATE_SEEKING)
   {
     mState = DECODER_STATE_COMPLETED;
-    ScheduleStateMachine();
+    mDecoder->GetReentrantMonitor().NotifyAll();
   }
 
-  LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get()));
+  LOG(PR_LOG_DEBUG, ("%p Shutting down DecodeLoop this=%p", mDecoder, this));
 }
 
 PRBool nsBuiltinDecoderStateMachine::IsPlaying()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   return !mPlayStartTime.IsNull();
 }
 
 void nsBuiltinDecoderStateMachine::AudioLoop()
 {
   NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
-  LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get()));
+  LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder));
   PRInt64 audioDuration = 0;
   PRInt64 audioStartTime = -1;
   PRUint32 channels, rate;
   double volume = -1;
   PRBool setVolume;
   PRInt32 minWriteSamples = -1;
   PRInt64 samplesAtLastSleep = 0;
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mAudioCompleted = PR_FALSE;
     audioStartTime = mAudioStartTime;
     channels = mInfo.mAudioChannels;
     rate = mInfo.mAudioRate;
     NS_ASSERTION(audioStartTime != -1, "Should have audio start time by now");
-
-    // We must hold the monitor while creating or destroying the audio stream,
-    // or whenever we use it off the audio thread.
-    mAudioStream = nsAudioStream::AllocateStream();
-    mAudioStream->Init(channels,
-                       rate,
-                       MOZ_SOUND_DATA_FORMAT);
-    volume = mVolume;
-    mAudioStream->SetVolume(volume);
   }
   while (1) {
 
     // Wait while we're not playing, and we're not shutting down, or we're
     // playing and we've got no audio to play.
     {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       NS_ASSERTION(mState != DECODER_STATE_DECODING_METADATA,
                    "Should have meta data before audio started playing.");
       while (mState != DECODER_STATE_SHUTDOWN &&
-             !mStopAudioThread &&
+             !mStopDecodeThreads &&
              (!IsPlaying() ||
               mState == DECODER_STATE_BUFFERING ||
               (mReader->mAudioQueue.GetSize() == 0 &&
                !mReader->mAudioQueue.AtEndOfStream())))
       {
-        if (!IsPlaying() && !mAudioStream->IsPaused()) {
-          mAudioStream->Pause();
-        }
         samplesAtLastSleep = audioDuration;
         mon.Wait();
       }
 
       // If we're shutting down, break out and exit the audio thread.
       if (mState == DECODER_STATE_SHUTDOWN ||
-          mStopAudioThread ||
+          mStopDecodeThreads ||
           mReader->mAudioQueue.AtEndOfStream())
       {
         break;
       }
 
-      // We only want to go to the expense of changing the volume if
-      // the volume has changed.
+      // We only want to go to the expense of taking the audio monitor and
+      // changing the volume if it's the first time we've entered the loop
+      // (as we must sync the volume in case it's changed since the
+      // nsAudioStream was created) or if the volume has changed.
       setVolume = volume != mVolume;
       volume = mVolume;
-
-      if (IsPlaying() && mAudioStream->IsPaused()) {
-        mAudioStream->Resume();
-      }
     }
 
-    if (setVolume) {
-      mAudioStream->SetVolume(volume);
-    }
-    if (minWriteSamples == -1) {
-      minWriteSamples = mAudioStream->GetMinWriteSamples();
+    if (setVolume || minWriteSamples == -1) {
+      ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
+      if (mAudioStream) {
+        if (setVolume) {
+          mAudioStream->SetVolume(volume);
+        }
+        if (minWriteSamples == -1) {
+          minWriteSamples = mAudioStream->GetMinWriteSamples();
+        }
+      }
     }
     NS_ASSERTION(mReader->mAudioQueue.GetSize() > 0,
                  "Should have data to play");
     // See if there's missing samples in the audio stream. If there is, push
     // silence into the audio hardware, so we can play across the gap.
     const SoundData* s = mReader->mAudioQueue.PeekFront();
 
     // Calculate the number of samples that have been pushed onto the audio
@@ -630,177 +534,210 @@ void nsBuiltinDecoderStateMachine::Audio
         // it's EOS flag set, and the decode thread sleeps just after decoding
         // that packet, but before realising there's no more packets.
         mon.NotifyAll();
       }
     }
   }
   if (mReader->mAudioQueue.AtEndOfStream() &&
       mState != DECODER_STATE_SHUTDOWN &&
-      !mStopAudioThread)
+      !mStopDecodeThreads)
   {
     // Last sample pushed to audio hardware, wait for the audio to finish,
     // before the audio thread terminates.
-    PRBool seeking = PR_FALSE;
-    {
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
+    if (mAudioStream) {
+      PRBool seeking = PR_FALSE;
       PRInt64 oldPosition = -1;
-      PRInt64 position = GetMediaTime();
-      while (oldPosition != position &&
-             mAudioEndTime - position > 0 &&
-             mState != DECODER_STATE_SEEKING &&
-             mState != DECODER_STATE_SHUTDOWN)
+
       {
-        const PRInt64 DRAIN_BLOCK_USECS = 100000;
-        Wait(NS_MIN(mAudioEndTime - position, DRAIN_BLOCK_USECS));
-        oldPosition = position;
-        position = GetMediaTime();
+        ReentrantMonitorAutoExit audioExit(mAudioReentrantMonitor);
+        ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+        PRInt64 position = GetMediaTime();
+        while (oldPosition != position &&
+               mAudioEndTime - position > 0 &&
+               mState != DECODER_STATE_SEEKING &&
+               mState != DECODER_STATE_SHUTDOWN)
+        {
+          const PRInt64 DRAIN_BLOCK_USECS = 100000;
+          Wait(NS_MIN(mAudioEndTime - position, DRAIN_BLOCK_USECS));
+          oldPosition = position;
+          position = GetMediaTime();
+        }
+        if (mState == DECODER_STATE_SEEKING) {
+          seeking = PR_TRUE;
+        }
       }
-      seeking = mState == DECODER_STATE_SEEKING;
-    }
+
+      if (!seeking && mAudioStream && !mAudioStream->IsPaused()) {
+        mAudioStream->Drain();
 
-    if (!seeking && !mAudioStream->IsPaused()) {
-      mAudioStream->Drain();
-      // Fire one last event for any extra samples that didn't fill a framebuffer.
-      mEventManager.Drain(mAudioEndTime);
+        // Fire one last event for any extra samples that didn't fill a framebuffer.
+        mEventManager.Drain(mAudioEndTime);
+      }
     }
+    LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
   }
-  LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder.get()));
   {
-    // Must hold lock while shutting down and anulling audio stream to prevent
-    // state machine thread trying to use it while we're destroying it.
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mAudioStream->Shutdown();
-    mAudioStream = nsnull;
-    mEventManager.Clear();
     mAudioCompleted = PR_TRUE;
     UpdateReadyState();
-    // Kick the decode thread; it may be sleeping waiting for this to finish.
+    // Kick the decode and state machine threads; they may be sleeping waiting
+    // for this to finish.
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
-  LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder.get()));
+  LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder));
 }
 
 PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples,
                                                    PRUint32 aChannels,
                                                    PRUint64 aSampleOffset)
 
 {
-  NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
-  NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
+  ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
+  if (!mAudioStream || mAudioStream->IsPaused()) {
+    // The state machine has paused since we've released the decoder
+    // monitor and acquired the audio monitor. Don't write any audio.
+    return 0;
+  }
   PRUint32 maxSamples = SILENCE_BYTES_CHUNK / aChannels;
   PRUint32 samples = NS_MIN(aSamples, maxSamples);
   PRUint32 numValues = samples * aChannels;
   nsAutoArrayPtr<SoundDataValue> buf(new SoundDataValue[numValues]);
   memset(buf.get(), 0, sizeof(SoundDataValue) * numValues);
   mAudioStream->Write(buf, numValues, PR_TRUE);
   // Dispatch events to the DOM for the audio just written.
   mEventManager.QueueWrittenAudioData(buf.get(), numValues,
                                       (aSampleOffset + samples) * aChannels);
   return samples;
 }
 
 PRUint32 nsBuiltinDecoderStateMachine::PlayFromAudioQueue(PRUint64 aSampleOffset,
                                                           PRUint32 aChannels)
 {
-  NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
-  NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
   nsAutoPtr<SoundData> sound(mReader->mAudioQueue.PopFront());
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     NS_WARN_IF_FALSE(IsPlaying(), "Should be playing");
     // Awaken the decode loop if it's waiting for space to free up in the
     // audio queue.
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
   PRInt64 offset = -1;
   PRUint32 samples = 0;
-  // The state machine could have paused since we've released the decoder
-  // monitor and acquired the audio monitor. Rather than acquire both
-  // monitors, the audio stream also maintains whether its paused or not.
-  // This prevents us from doing a blocking write while holding the audio
-  // monitor while paused; we would block, and the state machine won't be
-  // able to acquire the audio monitor in order to resume or destroy the
-  // audio stream.
-  if (!mAudioStream->IsPaused()) {
-    mAudioStream->Write(sound->mAudioData,
-                        sound->AudioDataLength(),
-                        PR_TRUE);
+  {
+    ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
+    if (!mAudioStream) {
+      return 0;
+    }
+    // The state machine could have paused since we've released the decoder
+    // monitor and acquired the audio monitor. Rather than acquire both
+    // monitors, the audio stream also maintains whether its paused or not.
+    // This prevents us from doing a blocking write while holding the audio
+    // monitor while paused; we would block, and the state machine won't be
+    // able to acquire the audio monitor in order to resume or destroy the
+    // audio stream.
+    if (!mAudioStream->IsPaused()) {
+      mAudioStream->Write(sound->mAudioData,
+                          sound->AudioDataLength(),
+                          PR_TRUE);
 
-    offset = sound->mOffset;
-    samples = sound->mSamples;
+      offset = sound->mOffset;
+      samples = sound->mSamples;
 
-    // Dispatch events to the DOM for the audio just written.
-    mEventManager.QueueWrittenAudioData(sound->mAudioData.get(),
-                                        sound->AudioDataLength(),
-                                        (aSampleOffset + samples) * aChannels);
-  } else {
-    mReader->mAudioQueue.PushFront(sound);
-    sound.forget();
+      // Dispatch events to the DOM for the audio just written.
+      mEventManager.QueueWrittenAudioData(sound->mAudioData.get(),
+                                          sound->AudioDataLength(),
+                                          (aSampleOffset + samples) * aChannels);
+    } else {
+      mReader->mAudioQueue.PushFront(sound);
+      sound.forget();
+    }
   }
   if (offset != -1) {
     mDecoder->UpdatePlaybackOffset(offset);
   }
   return samples;
 }
 
 nsresult nsBuiltinDecoderStateMachine::Init(nsDecoderStateMachine* aCloneDonor)
 {
   nsBuiltinDecoderReader* cloneReader = nsnull;
   if (aCloneDonor) {
     cloneReader = static_cast<nsBuiltinDecoderStateMachine*>(aCloneDonor)->mReader;
   }
   return mReader->Init(cloneReader);
 }
 
-void nsBuiltinDecoderStateMachine::StopPlayback()
+void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
 {
-  LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get()));
-
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   mDecoder->mPlaybackStatistics.Stop(TimeStamp::Now());
 
   // Reset mPlayStartTime before we pause/shutdown the nsAudioStream. This is
   // so that if the audio loop is about to write audio, it will have the chance
   // to check to see if we're paused and not write the audio. If not, the
   // audio thread can block in the write, and we deadlock trying to acquire
   // the audio monitor upon resume playback.
   if (IsPlaying()) {
     mPlayDuration += DurationToUsecs(TimeStamp::Now() - mPlayStartTime);
     mPlayStartTime = TimeStamp();
   }
-  // Notify the audio thread, so that it notices that we've stopped playing,
-  // so it can pause audio playback.
-  mDecoder->GetReentrantMonitor().NotifyAll();
-  NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()");
+  if (HasAudio()) {
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
+    if (mAudioStream) {
+      if (aMode == AUDIO_PAUSE) {
+        mAudioStream->Pause();
+      } else if (aMode == AUDIO_SHUTDOWN) {
+        mAudioStream->Shutdown();
+        mAudioStream = nsnull;
+        mEventManager.Clear();
+      }
+    }
+  }
 }
 
 void nsBuiltinDecoderStateMachine::StartPlayback()
 {
-  LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get()));
-
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
   NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder.get()));
+  LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder));
   mDecoder->mPlaybackStatistics.Start(TimeStamp::Now());
-  mPlayStartTime = TimeStamp::Now();
+  if (HasAudio()) {
+    PRInt32 rate = mInfo.mAudioRate;
+    PRInt32 channels = mInfo.mAudioChannels;
 
-  NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
-  if (NS_FAILED(StartAudioThread())) {
-    NS_WARNING("Failed to create audio thread"); 
+    {
+      ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+      ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
+      if (mAudioStream) {
+        // We have an audiostream, so it must have been paused the last time
+        // StopPlayback() was called.
+        mAudioStream->Resume();
+      } else {
+        // No audiostream, create one.
+        mAudioStream = nsAudioStream::AllocateStream();
+        mAudioStream->Init(channels, rate, MOZ_SOUND_DATA_FORMAT);
+        mAudioStream->SetVolume(mVolume);
+      }
+    }
   }
+  mPlayStartTime = TimeStamp::Now();
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void nsBuiltinDecoderStateMachine::UpdatePlaybackPositionInternal(PRInt64 aTime)
 {
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
   mCurrentFrameTime = aTime - mStartTime;
   NS_ASSERTION(mCurrentFrameTime >= 0, "CurrentTime should be positive!");
   if (aTime > mEndTime) {
     NS_ASSERTION(mCurrentFrameTime > GetDuration(),
@@ -851,17 +788,17 @@ void nsBuiltinDecoderStateMachine::SetVo
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mVolume = volume;
 }
 
 double nsBuiltinDecoderStateMachine::GetCurrentTime() const
 {
   NS_ASSERTION(NS_IsMainThread() ||
-               OnStateMachineThread() ||
+               mDecoder->OnStateMachineThread() ||
                OnDecodeThread(),
                "Should be on main, decode, or state machine thread.");
 
   return static_cast<double>(mCurrentFrameTime) / static_cast<double>(USECS_PER_S);
 }
 
 PRInt64 nsBuiltinDecoderStateMachine::GetDuration()
 {
@@ -869,35 +806,35 @@ PRInt64 nsBuiltinDecoderStateMachine::Ge
 
   if (mEndTime == -1 || mStartTime == -1)
     return -1;
   return mEndTime - mStartTime;
 }
 
 void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
 {
-  NS_ASSERTION(NS_IsMainThread() || OnDecodeThread(),
-               "Should be on main or decode thread.");
+  NS_ASSERTION(NS_IsMainThread() || mDecoder->OnStateMachineThread(),
+    "Should be on main or state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (aDuration == -1) {
     return;
   }
 
   if (mStartTime != -1) {
     mEndTime = mStartTime + aDuration;
   } else {
     mStartTime = 0;
     mEndTime = aDuration;
   }
 }
 
 void nsBuiltinDecoderStateMachine::SetEndTime(PRInt64 aEndTime)
 {
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread");
+  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   mEndTime = aEndTime;
 }
 
 void nsBuiltinDecoderStateMachine::SetSeekable(PRBool aSeekable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@@ -910,52 +847,51 @@ void nsBuiltinDecoderStateMachine::Shutd
 {
   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.
-  LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
-  ScheduleStateMachine();
+  LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder));
   mState = DECODER_STATE_SHUTDOWN;
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void nsBuiltinDecoderStateMachine::StartDecoding()
 {
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState != DECODER_STATE_DECODING) {
     mDecodeStartTime = TimeStamp::Now();
   }
   mState = DECODER_STATE_DECODING;
-  ScheduleStateMachine();
 }
 
 void nsBuiltinDecoderStateMachine::Play()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   // When asked to play, switch to decoding state only if
   // we are currently buffering. In other cases, we'll start playing anyway
   // when the state machine notices the decoder's state change to PLAYING.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
-    LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get()));
+    LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
     mState = DECODER_STATE_DECODING;
     mDecodeStartTime = TimeStamp::Now();
+    mDecoder->GetReentrantMonitor().NotifyAll();
   }
-  ScheduleStateMachine();
 }
 
 void nsBuiltinDecoderStateMachine::ResetPlayback()
 {
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
   mVideoFrameEndTime = -1;
   mAudioStartTime = -1;
   mAudioEndTime = -1;
   mAudioCompleted = PR_FALSE;
 }
 
 void nsBuiltinDecoderStateMachine::Seek(double aTime)
 {
@@ -977,83 +913,60 @@ void nsBuiltinDecoderStateMachine::Seek(
   NS_ASSERTION(mSeekTime >= mStartTime && mSeekTime <= mEndTime,
                "Can only seek in range [0,duration]");
 
   // Bound the seek time to be inside the media range.
   NS_ASSERTION(mStartTime != -1, "Should know start time by now");
   NS_ASSERTION(mEndTime != -1, "Should know end time by now");
   mSeekTime = NS_MIN(mSeekTime, mEndTime);
   mSeekTime = NS_MAX(mStartTime, mSeekTime);
-  LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime));
+  LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder, aTime));
   mState = DECODER_STATE_SEEKING;
-  ScheduleStateMachine();
 }
 
-void nsBuiltinDecoderStateMachine::StopDecodeThread()
+void nsBuiltinDecoderStateMachine::StopDecodeThreads()
 {
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  mStopDecodeThread = PR_TRUE;
+  mStopDecodeThreads = PR_TRUE;
   mDecoder->GetReentrantMonitor().NotifyAll();
   if (mDecodeThread) {
-    LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder.get()));
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       mDecodeThread->Shutdown();
     }
     mDecodeThread = nsnull;
-    mDecodeThreadIdle = PR_FALSE;
   }
-}
-
-void nsBuiltinDecoderStateMachine::StopAudioThread()
-{
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  mStopAudioThread = PR_TRUE;
-  mDecoder->GetReentrantMonitor().NotifyAll();
   if (mAudioThread) {
-    LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder.get()));
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       mAudioThread->Shutdown();
     }
     mAudioThread = nsnull;
   }
 }
 
 nsresult
-nsBuiltinDecoderStateMachine::StartDecodeThread()
+nsBuiltinDecoderStateMachine::StartDecodeThreads()
 {
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  mStopDecodeThread = PR_FALSE;
-  if ((mDecodeThread && !mDecodeThreadIdle) || mState >= DECODER_STATE_COMPLETED)
-    return NS_OK;
-
-  if (!mDecodeThread) {
+  mStopDecodeThreads = PR_FALSE;
+  if (!mDecodeThread && mState < DECODER_STATE_COMPLETED) {
     nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
     if (NS_FAILED(rv)) {
       mState = DECODER_STATE_SHUTDOWN;
       return rv;
     }
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::DecodeLoop);
+    mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::DecodeThreadRun);
-  mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
-  mDecodeThreadIdle = PR_FALSE;
-  return NS_OK;
-}
-
-nsresult
-nsBuiltinDecoderStateMachine::StartAudioThread()
-{
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  mStopAudioThread = PR_FALSE;
   if (HasAudio() && !mAudioThread) {
     nsresult rv = NS_NewThread(getter_AddRefs(mAudioThread));
     if (NS_FAILED(rv)) {
       mState = DECODER_STATE_SHUTDOWN;
       return rv;
     }
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::AudioLoop);
@@ -1129,608 +1042,518 @@ PRInt64 nsBuiltinDecoderStateMachine::Ge
 void nsBuiltinDecoderStateMachine::SetFrameBufferLength(PRUint32 aLength)
 {
   NS_ASSERTION(aLength >= 512 && aLength <= 16384,
                "The length must be between 512 and 16384");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   mEventManager.SetSignalBufferLength(aLength);
 }
 
-nsresult nsBuiltinDecoderStateMachine::DecodeMetadata()
-{
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(mState == DECODER_STATE_DECODING_METADATA,
-               "Only call when in metadata decoding state");
-
-  LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get()));
-  nsresult res;
-  nsVideoInfo info;
-  {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    res = mReader->ReadMetadata(&info);
-  }
-  mInfo = info;
-
-  if (NS_FAILED(res) || (!info.mHasVideo && !info.mHasAudio)) {
-    // 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. Note the state machine shutdown joins this decode
-    // thread during shutdown (and other state machines can run on the state
-    // machine thread while the join is waiting), so it's safe to do this
-    // synchronously.
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DecodeError);
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
-    return NS_ERROR_FAILURE;
-  }
-  mDecoder->StartProgressUpdates();
-  mGotDurationFromMetaData = (GetDuration() != -1);
-
-  VideoData* videoData = FindStartTime();
-  if (videoData) {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    RenderVideoFrame(videoData, TimeStamp::Now());
-  }
-
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_ASSERTION(mStartTime != -1, "Must have start time");
-  NS_ASSERTION((!HasVideo() && !HasAudio()) ||
-                !mSeekable || mEndTime != -1,
-                "Active seekable media should have end time");
-  NS_ASSERTION(!mSeekable || GetDuration() != -1, "Seekable media should have duration");
-  LOG(PR_LOG_DEBUG, ("%p Media goes from %lld to %lld (duration %lld) seekable=%d",
-                      mDecoder.get(), mStartTime, mEndTime, GetDuration(), mSeekable));
-
-  // Inform the element that we've loaded the metadata and the first frame,
-  // setting the default framebuffer size for audioavailable events.  Also,
-  // if there is audio, let the MozAudioAvailable event manager know about
-  // the metadata.
-  if (HasAudio()) {
-    mEventManager.Init(mInfo.mAudioChannels, mInfo.mAudioRate);
-    // Set the buffer length at the decoder level to be able, to be able
-    // to retrive the value via media element method. The RequestFrameBufferLength
-    // will call the nsBuiltinDecoderStateMachine::SetFrameBufferLength().
-    PRUint32 frameBufferLength = mInfo.mAudioChannels * FRAMEBUFFER_LENGTH_PER_CHANNEL;
-    mDecoder->RequestFrameBufferLength(frameBufferLength);
-  }
-  nsCOMPtr<nsIRunnable> metadataLoadedEvent =
-    new nsAudioMetadataEventRunner(mDecoder, mInfo.mAudioChannels, mInfo.mAudioRate);
-  NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
-
-  if (mState == DECODER_STATE_DECODING_METADATA) {
-    LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get()));
-    StartDecoding();
-  }
-
-  if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_COMPLETED) &&
-      mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING &&
-      !IsPlaying())
-  {
-    StartPlayback();
-  }
-
-  return NS_OK;
-}
-
-void nsBuiltinDecoderStateMachine::DecodeSeek()
+nsresult nsBuiltinDecoderStateMachine::Run()
 {
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(mState == DECODER_STATE_SEEKING,
-               "Only call when in seeking state");
-
-  // During the seek, don't have a lock on the decoder state,
-  // otherwise long seek operations can block the main thread.
-  // The events dispatched to the main thread are SYNC calls.
-  // These calls are made outside of the decode monitor lock so
-  // it is safe for the main thread to makes calls that acquire
-  // the lock since it won't deadlock. We check the state when
-  // acquiring the lock again in case shutdown has occurred
-  // during the time when we didn't have the lock.
-  PRInt64 seekTime = mSeekTime;
-  mDecoder->StopProgressUpdates();
-
-  PRBool currentTimeChanged = false;
-  PRInt64 mediaTime = GetMediaTime();
-  if (mediaTime != seekTime) {
-    currentTimeChanged = true;
-    UpdatePlaybackPositionInternal(seekTime);
-  }
-
-  // SeekingStarted will do a UpdateReadyStateForData which will
-  // inform the element and its users that we have no frames
-  // to display
-  {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    nsCOMPtr<nsIRunnable> startEvent =
-      NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStarted);
-    NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
-  }
-
-  if (currentTimeChanged) {
-    // The seek target is different than the current playback position,
-    // we'll need to seek the playback position, so shutdown our decode
-    // and audio threads.
-    StopPlayback();
-    StopAudioThread();
-    ResetPlayback();
-    nsresult res;
-    {
-      ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-      // Now perform the seek. We must not hold the state machine monitor
-      // while we seek, since the seek reads, which could block on I/O.
-      res = mReader->Seek(seekTime,
-                          mStartTime,
-                          mEndTime,
-                          mediaTime);
-    }
-    if (NS_SUCCEEDED(res)) {
-      SoundData* audio = HasAudio() ? mReader->mAudioQueue.PeekFront() : nsnull;
-      NS_ASSERTION(!audio || (audio->mTime <= seekTime &&
-                              seekTime <= audio->mTime + audio->mDuration),
-                    "Seek target should lie inside the first audio block after seek");
-      PRInt64 startTime = (audio && audio->mTime < seekTime) ? audio->mTime : seekTime;
-      mAudioStartTime = startTime;
-      mPlayDuration = startTime - mStartTime;
-      if (HasVideo()) {
-        nsAutoPtr<VideoData> video(mReader->mVideoQueue.PeekFront());
-        if (video) {
-          NS_ASSERTION(video->mTime <= seekTime && seekTime <= video->mEndTime,
-                        "Seek target should lie inside the first frame after seek");
-          {
-            ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-            RenderVideoFrame(video, TimeStamp::Now());
-          }
-          mReader->mVideoQueue.PopFront();
-          nsCOMPtr<nsIRunnable> event =
-            NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::Invalidate);
-          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-        }
-      }
-    }
-  }
-  mDecoder->StartProgressUpdates();
-  if (mState == DECODER_STATE_SHUTDOWN)
-    return;
-
-  // Try to decode another frame to detect if we're at the end...
-  LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n",
-      mDecoder.get(), mCurrentFrameTime));
-
-  // Change state to DECODING or COMPLETED now. SeekingStopped will
-  // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
-  // if we need to seek again.
-
-  nsCOMPtr<nsIRunnable> stopEvent;
-  if (GetMediaTime() == mEndTime) {
-    LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
-                        mDecoder.get(), seekTime));
-    stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd);
-    mState = DECODER_STATE_COMPLETED;
-  } else {
-    LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING",
-                        mDecoder.get(), seekTime));
-    stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStopped);
-    StartDecoding();
-  }
-  {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
-  }
-
-  // Reset quick buffering status. This ensures that if we began the
-  // seek while quick-buffering, we won't bypass quick buffering mode
-  // if we need to buffer after the seek.
-  mQuickBuffering = PR_FALSE;
-
-  ScheduleStateMachine();
-}
-
-// Runnable to dispose of the decoder and state machine on the main thread.
-class nsDecoderDisposeEvent : public nsRunnable {
-public:
-  nsDecoderDisposeEvent(already_AddRefed<nsBuiltinDecoder> aDecoder)
-    : mDecoder(aDecoder) {}
-  NS_IMETHOD Run() {
-    NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
-    mDecoder = nsnull;
-    return NS_OK;
-  }
-private:
-  nsRefPtr<nsBuiltinDecoder> mDecoder;
-};
-
-// Runnable which dispatches an event to the main thread to dispose of the
-// decoder and state machine. This runs on the state machine thread after
-// the state machine has shutdown, and all events for that state machine have
-// finished running.
-class nsDispatchDisposeEvent : public nsRunnable {
-public:
-  nsDispatchDisposeEvent(already_AddRefed<nsBuiltinDecoder> aDecoder)
-    : mDecoder(aDecoder) {}
-  NS_IMETHOD Run() {
-    NS_DispatchToMainThread(new nsDecoderDisposeEvent(mDecoder.forget()),
-                            NS_DISPATCH_NORMAL);
-    return NS_OK;
-  }
-private:
-  nsRefPtr<nsBuiltinDecoder> mDecoder;
-};
-
-nsresult nsBuiltinDecoderStateMachine::RunStateMachine()
-{
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
 
-  switch (mState) {
-    case DECODER_STATE_SHUTDOWN: {
+  while (PR_TRUE) {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    switch (mState) {
+    case DECODER_STATE_SHUTDOWN:
       if (IsPlaying()) {
-        StopPlayback();
+        StopPlayback(AUDIO_SHUTDOWN);
       }
-      StopAudioThread();
-      StopDecodeThread();
+      StopDecodeThreads();
       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
-      // 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.
-      NS_DispatchToCurrentThread(
-        new nsDispatchDisposeEvent(mDecoder.forget()));
       return NS_OK;
-    }
+
+    case DECODER_STATE_DECODING_METADATA:
+      {
+        LoadMetadata();
+        if (mState == DECODER_STATE_SHUTDOWN) {
+          continue;
+        }
+
+        VideoData* videoData = FindStartTime();
+        if (videoData) {
+          ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+          RenderVideoFrame(videoData, TimeStamp::Now());
+        }
+
+        // Start the decode threads, so that we can pre buffer the streams.
+        // and calculate the start time in order to determine the duration.
+        if (NS_FAILED(StartDecodeThreads())) {
+          continue;
+        }
+
+        NS_ASSERTION(mStartTime != -1, "Must have start time");
+        NS_ASSERTION((!HasVideo() && !HasAudio()) ||
+                     !mSeekable || mEndTime != -1,
+                     "Active seekable media should have end time");
+        NS_ASSERTION(!mSeekable || GetDuration() != -1, "Seekable media should have duration");
+        LOG(PR_LOG_DEBUG, ("%p Media goes from %lld to %lld (duration %lld) seekable=%d",
+                           mDecoder, mStartTime, mEndTime, GetDuration(), mSeekable));
+
+        if (mState == DECODER_STATE_SHUTDOWN)
+          continue;
 
-    case DECODER_STATE_DECODING_METADATA: {
-      // Ensure we have a decode thread to decode metadata.
-      return StartDecodeThread();
-    }
-  
-    case DECODER_STATE_DECODING: {
-      if (mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING &&
-          IsPlaying())
+        // Inform the element that we've loaded the metadata and the first frame,
+        // setting the default framebuffer size for audioavailable events.  Also,
+        // if there is audio, let the MozAudioAvailable event manager know about
+        // the metadata.
+        if (HasAudio()) {
+          mEventManager.Init(mInfo.mAudioChannels, mInfo.mAudioRate);
+          // Set the buffer length at the decoder level to be able, to be able
+          // to retrive the value via media element method. The RequestFrameBufferLength
+          // will call the nsBuiltinDecoderStateMachine::SetFrameBufferLength().
+          PRUint32 frameBufferLength = mInfo.mAudioChannels * FRAMEBUFFER_LENGTH_PER_CHANNEL;
+          mDecoder->RequestFrameBufferLength(frameBufferLength);
+        }
+        nsCOMPtr<nsIRunnable> metadataLoadedEvent =
+          new nsAudioMetadataEventRunner(mDecoder, mInfo.mAudioChannels, mInfo.mAudioRate);
+        NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
+
+        if (mState == DECODER_STATE_DECODING_METADATA) {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
+          StartDecoding();
+        }
+
+        // Start playback.
+        if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
+          if (!IsPlaying()) {
+            StartPlayback();
+          }
+        }
+      }
+      break;
+
+    case DECODER_STATE_DECODING:
+      {
+        if (NS_FAILED(StartDecodeThreads())) {
+          continue;
+        }
+
+        AdvanceFrame();
+
+        if (mState != DECODER_STATE_DECODING)
+          continue;
+      }
+      break;
+
+    case DECODER_STATE_SEEKING:
       {
-        // We're playing, but the element/decoder is in paused state. Stop
-        // playing! Note we do this before StopDecodeThread() below because
-        // that blocks this state machine's execution, and can cause a
-        // perceptible delay between the pause command, and playback actually
-        // pausing.
-        StopPlayback();
-      }
+        // During the seek, don't have a lock on the decoder state,
+        // otherwise long seek operations can block the main thread.
+        // The events dispatched to the main thread are SYNC calls.
+        // These calls are made outside of the decode monitor lock so
+        // it is safe for the main thread to makes calls that acquire
+        // the lock since it won't deadlock. We check the state when
+        // acquiring the lock again in case shutdown has occurred
+        // during the time when we didn't have the lock.
+        PRInt64 seekTime = mSeekTime;
+        mDecoder->StopProgressUpdates();
+
+        PRBool currentTimeChanged = false;
+        PRInt64 mediaTime = GetMediaTime();
+        if (mediaTime != seekTime) {
+          currentTimeChanged = true;
+          UpdatePlaybackPositionInternal(seekTime);
+        }
+
+        // SeekingStarted will do a UpdateReadyStateForData which will
+        // inform the element and its users that we have no frames
+        // to display
+        {
+          ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+          nsCOMPtr<nsIRunnable> startEvent =
+            NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStarted);
+          NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
+        }
 
-      if (IsPausedAndDecoderWaiting()) {
-        // The decode buffers are full, and playback is paused. Shutdown the
-        // decode thread.
-        StopDecodeThread();
-        return NS_OK;
-      }
+        if (currentTimeChanged) {
+          // The seek target is different than the current playback position,
+          // we'll need to seek the playback position, so shutdown our decode
+          // and audio threads.
+          StopPlayback(AUDIO_SHUTDOWN);
+          StopDecodeThreads();
+          ResetPlayback();
+          nsresult res;
+          {
+            ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+            // Now perform the seek. We must not hold the state machine monitor
+            // while we seek, since the seek decodes.
+            res = mReader->Seek(seekTime,
+                                mStartTime,
+                                mEndTime,
+                                mediaTime);
+          }
+          if (NS_SUCCEEDED(res)){
+            SoundData* audio = HasAudio() ? mReader->mAudioQueue.PeekFront() : nsnull;
+            NS_ASSERTION(!audio || (audio->mTime <= seekTime &&
+                                    seekTime <= audio->mTime + audio->mDuration),
+                         "Seek target should lie inside the first audio block after seek");
+            PRInt64 startTime = (audio && audio->mTime < seekTime) ? audio->mTime : seekTime;
+            mAudioStartTime = startTime;
+            mPlayDuration = startTime - mStartTime;
+            if (HasVideo()) {
+              nsAutoPtr<VideoData> video(mReader->mVideoQueue.PeekFront());
+              if (video) {
+                NS_ASSERTION(video->mTime <= seekTime && seekTime <= video->mEndTime,
+                             "Seek target should lie inside the first frame after seek");
+                {
+                  ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+                  RenderVideoFrame(video, TimeStamp::Now());
+                }
+                mReader->mVideoQueue.PopFront();
+                nsCOMPtr<nsIRunnable> event =
+                  NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::Invalidate);
+                NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+              }
+            }
+          }
+        }
+        mDecoder->StartProgressUpdates();
+        if (mState == DECODER_STATE_SHUTDOWN)
+          continue;
+
+        // Try to decode another frame to detect if we're at the end...
+        LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n", mDecoder, mCurrentFrameTime));
 
-      // We're playing and/or our decode buffers aren't full. Ensure we have
-      // an active decode thread.
-      if (NS_FAILED(StartDecodeThread())) {
-        NS_WARNING("Failed to start media decode thread!");
-        return NS_ERROR_FAILURE;
+        // Change state to DECODING or COMPLETED now. SeekingStopped will
+        // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
+        // if we need to seek again.
+        
+        nsCOMPtr<nsIRunnable> stopEvent;
+        if (GetMediaTime() == mEndTime) {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
+                             mDecoder, seekTime));
+          stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd);
+          mState = DECODER_STATE_COMPLETED;
+        } else {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING",
+                             mDecoder, seekTime));
+          stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStopped);
+          StartDecoding();
+        }
+        mDecoder->GetReentrantMonitor().NotifyAll();
+
+        {
+          ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+          NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
+        }
+
+        // Reset quick buffering status. This ensures that if we began the
+        // seek while quick-buffering, we won't bypass quick buffering mode
+        // if we need to buffer after the seek.
+        mQuickBuffering = PR_FALSE;
       }
+      break;
+
+    case DECODER_STATE_BUFFERING:
+      {
+        if (IsPlaying()) {
+          StopPlayback(AUDIO_PAUSE);
+          mDecoder->GetReentrantMonitor().NotifyAll();
+        }
+
+        TimeStamp now = TimeStamp::Now();
+        NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
 
-      AdvanceFrame();
-      NS_ASSERTION(mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING ||
-                   IsStateMachineScheduled(), "Must have timer scheduled");
-      return NS_OK;
-    }
+        // We will remain in the buffering state if we've not decoded enough
+        // data to begin playback, or if we've not downloaded a reasonable
+        // amount of data inside our buffering time.
+        TimeDuration elapsed = now - mBufferingStart;
+        PRBool isLiveStream = mDecoder->GetCurrentStream()->GetLength() == -1;
+        if ((isLiveStream || !mDecoder->CanPlayThrough()) &&
+             elapsed < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
+             (mQuickBuffering ? HasLowDecodedData(QUICK_BUFFERING_LOW_DATA_USECS)
+                              : (GetUndecodedData() < BUFFERING_WAIT * USECS_PER_S)) &&
+             !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+             !stream->IsSuspended())
+        {
+          LOG(PR_LOG_DEBUG,
+              ("Buffering: %.3lfs/%ds, timeout in %.3lfs %s",
+               GetUndecodedData() / static_cast<double>(USECS_PER_S),
+               BUFFERING_WAIT,
+               BUFFERING_WAIT - elapsed.ToSeconds(),
+               (mQuickBuffering ? "(quick exit)" : "")));
+          Wait(USECS_PER_S);
+          if (mState == DECODER_STATE_SHUTDOWN)
+            continue;
+        } else {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
+          LOG(PR_LOG_DEBUG, ("%p Buffered for %.3lfs",
+                             mDecoder,
+                             (now - mBufferingStart).ToSeconds()));
+          StartDecoding();
+        }
 
-    case DECODER_STATE_BUFFERING: {
-      if (IsPausedAndDecoderWaiting()) {
-        // The decode buffers are full, and playback is paused. Shutdown the
-        // decode thread.
-        StopDecodeThread();
-        return NS_OK;
+        if (mState != DECODER_STATE_BUFFERING) {
+          // Notify to allow blocked decoder thread to continue
+          mDecoder->GetReentrantMonitor().NotifyAll();
+          UpdateReadyState();
+          if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
+            if (!IsPlaying()) {
+              StartPlayback();
+            }
+          }
+        }
+        break;
       }
 
-      TimeStamp now = TimeStamp::Now();
-      NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
-
-      // We will remain in the buffering state if we've not decoded enough
-      // data to begin playback, or if we've not downloaded a reasonable
-      // amount of data inside our buffering time.
-      TimeDuration elapsed = now - mBufferingStart;
-      PRBool isLiveStream = mDecoder->GetCurrentStream()->GetLength() == -1;
-      if ((isLiveStream || !mDecoder->CanPlayThrough()) &&
-            elapsed < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
-            (mQuickBuffering ? HasLowDecodedData(QUICK_BUFFERING_LOW_DATA_USECS)
-                            : (GetUndecodedData() < BUFFERING_WAIT * USECS_PER_S)) &&
-            !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
-            !stream->IsSuspended())
+    case DECODER_STATE_COMPLETED:
       {
-        LOG(PR_LOG_DEBUG,
-            ("%p Buffering: %.3lfs/%ds, timeout in %.3lfs %s",
-              mDecoder.get(),
-              GetUndecodedData() / static_cast<double>(USECS_PER_S),
-              BUFFERING_WAIT,
-              BUFFERING_WAIT - elapsed.ToSeconds(),
-              (mQuickBuffering ? "(quick exit)" : "")));
-        ScheduleStateMachine(USECS_PER_S);
-        return NS_OK;
-      } else {
-        LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get()));
-        LOG(PR_LOG_DEBUG, ("%p Buffered for %.3lfs",
-                            mDecoder.get(),
-                            (now - mBufferingStart).ToSeconds()));
-        StartDecoding();
-      }
+        if (NS_FAILED(StartDecodeThreads())) {
+          continue;
+        }
 
-      // Notify to allow blocked decoder thread to continue
-      mDecoder->GetReentrantMonitor().NotifyAll();
-      UpdateReadyState();
-      if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING &&
-          !IsPlaying())
-      {
-        StartPlayback();
-      }
-      NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
-      return NS_OK;
-    }
+        // Play the remaining media. We want to run AdvanceFrame() at least
+        // once to ensure the current playback position is advanced to the
+        // end of the media, and so that we update the readyState.
+        do {
+          AdvanceFrame();
+        } while (mState == DECODER_STATE_COMPLETED &&
+                 (mReader->mVideoQueue.GetSize() > 0 ||
+                 (HasAudio() && !mAudioCompleted)));
+
+        if (mAudioStream) {
+          // Close the audio stream so that next time audio is used a new stream
+          // is created. The StopPlayback call also resets the IsPlaying() state
+          // so audio is restarted correctly.
+          StopPlayback(AUDIO_SHUTDOWN);
+        }
+
+        if (mState != DECODER_STATE_COMPLETED)
+          continue;
 
-    case DECODER_STATE_SEEKING: {
-      // Ensure we have a decode thread to perform the seek.
-     return StartDecodeThread();
-    }
-
-    case DECODER_STATE_COMPLETED: {
-      StopDecodeThread();
-
-      if (mState != DECODER_STATE_COMPLETED) {
-        // While we're waiting for the decode thread to shutdown, we can
-        // change state, for example to seeking or shutdown state.
-        // Whatever changed our state should have scheduled another state
-        // machine run.
-        NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
-        return NS_OK;
-      }
+        LOG(PR_LOG_DEBUG, ("%p Shutting down the state machine thread", mDecoder));
+        StopDecodeThreads();
 
-      // Play the remaining media. We want to run AdvanceFrame() at least
-      // once to ensure the current playback position is advanced to the
-      // end of the media, and so that we update the readyState.
-      if (mState == DECODER_STATE_COMPLETED &&
-          (mReader->mVideoQueue.GetSize() > 0 ||
-          (HasAudio() && !mAudioCompleted)))
-      {
-        AdvanceFrame();
-        NS_ASSERTION(mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING ||
-                     IsStateMachineScheduled(),
-                     "Must have timer scheduled");
-        return NS_OK;
-      }
-
-      // StopPlayback in order to reset the IsPlaying() state so audio
-      // is restarted correctly.
-      StopPlayback();
+        if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
+          PRInt64 videoTime = HasVideo() ? mVideoFrameEndTime : 0;
+          PRInt64 clockTime = NS_MAX(mEndTime, NS_MAX(videoTime, GetAudioClock()));
+          UpdatePlaybackPosition(clockTime);
+          {
+            ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+            nsCOMPtr<nsIRunnable> event =
+              NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackEnded);
+            NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+          }
+        }
 
-      if (mState != DECODER_STATE_COMPLETED) {
-        // While we're presenting a frame we can change state. Whatever changed
-        // our state should have scheduled another state machine run.
-        NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
-        return NS_OK;
+        if (mState == DECODER_STATE_COMPLETED) {
+          // We've finished playback. Shutdown the state machine thread, 
+          // in order to save memory on thread stacks, particuarly on Linux.
+          nsCOMPtr<nsIRunnable> event =
+            new ShutdownThreadEvent(mDecoder->mStateMachineThread);
+          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+          mDecoder->mStateMachineThread = nsnull;
+          return NS_OK;
+        }
       }
- 
-      StopAudioThread();
-      if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
-        PRInt64 videoTime = HasVideo() ? mVideoFrameEndTime : 0;
-        PRInt64 clockTime = NS_MAX(mEndTime, NS_MAX(videoTime, GetAudioClock()));
-        UpdatePlaybackPosition(clockTime);
-        nsCOMPtr<nsIRunnable> event =
-          NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackEnded);
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      }
-      return NS_OK;
+      break;
     }
   }
 
   return NS_OK;
 }
 
 void nsBuiltinDecoderStateMachine::RenderVideoFrame(VideoData* aData,
                                                     TimeStamp aTarget)
 {
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
 
   if (aData->mDuplicate) {
     return;
   }
 
   nsRefPtr<Image> image = aData->mImage;
   if (image) {
     mDecoder->SetVideoData(aData->mDisplay, image, aTarget);
   }
 }
 
 PRInt64
 nsBuiltinDecoderStateMachine::GetAudioClock()
 {
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  if (!HasAudio())
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
+  if (!mAudioStream || !HasAudio())
     return -1;
-  if (!mAudioStream) {
-    // Audio thread hasn't played any data yet.
-    return mAudioStartTime;
-  }
   PRInt64 t = mAudioStream->GetPosition();
   return (t == -1) ? -1 : t + mAudioStartTime;
 }
 
 void nsBuiltinDecoderStateMachine::AdvanceFrame()
 {
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(!HasAudio() || mAudioStartTime != -1,
-               "Should know audio start time if we have audio.");
-
-  if (mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING) {
-    return;
-  }
 
-  // Determine the clock time. If we've got audio, and we've not reached
-  // the end of the audio, use the audio clock. However if we've finished
-  // audio, or don't have audio, use the system clock.
-  PRInt64 clock_time = -1;
-  if (!IsPlaying()) {
-    clock_time = mPlayDuration + mStartTime;
-  } else {
-    PRInt64 audio_time = GetAudioClock();
-    if (HasAudio() && !mAudioCompleted && audio_time != -1) {
-      clock_time = audio_time;
-      // Resync against the audio clock, while we're trusting the
-      // audio clock. This ensures no "drift", particularly on Linux.
-      mPlayDuration = clock_time - mStartTime;
-      mPlayStartTime = TimeStamp::Now();
+  // When it's time to display a frame, decode the frame and display it.
+  if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
+    if (HasAudio() && mAudioStartTime == -1 && !mAudioCompleted) {
+      // We've got audio (so we should sync off the audio clock), but we've not
+      // played a sample on the audio thread, so we can't get a time from the
+      // audio clock. Just wait and then return, to give the audio clock time
+      // to tick.  This should really wait for a specific signal from the audio
+      // thread rather than polling after a sleep.  See bug 568431 comment 4.
+      Wait(AUDIO_DURATION_USECS);
+      return;
+    }
+
+    // Determine the clock time. If we've got audio, and we've not reached
+    // the end of the audio, use the audio clock. However if we've finished
+    // audio, or don't have audio, use the system clock.
+    PRInt64 clock_time = -1;
+    if (!IsPlaying()) {
+      clock_time = mPlayDuration + mStartTime;
     } else {
-      // Sound is disabled on this system. Sync to the system clock.
-      clock_time = DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration;
-      // Ensure the clock can never go backwards.
-      NS_ASSERTION(mCurrentFrameTime <= clock_time, "Clock should go forwards");
-      clock_time = NS_MAX(mCurrentFrameTime, clock_time) + mStartTime;
+      PRInt64 audio_time = GetAudioClock();
+      if (HasAudio() && !mAudioCompleted && audio_time != -1) {
+        clock_time = audio_time;
+        // Resync against the audio clock, while we're trusting the
+        // audio clock. This ensures no "drift", particularly on Linux.
+        mPlayDuration = clock_time - mStartTime;
+        mPlayStartTime = TimeStamp::Now();
+      } else {
+        // Sound is disabled on this system. Sync to the system clock.
+        clock_time = DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration;
+        // Ensure the clock can never go backwards.
+        NS_ASSERTION(mCurrentFrameTime <= clock_time, "Clock should go forwards");
+        clock_time = NS_MAX(mCurrentFrameTime, clock_time) + mStartTime;
+      }
     }
-  }
+
+    // Skip frames up to the frame at the playback position, and figure out
+    // the time remaining until it's time to display the next frame.
+    PRInt64 remainingTime = AUDIO_DURATION_USECS;
+    NS_ASSERTION(clock_time >= mStartTime, "Should have positive clock time.");
+    nsAutoPtr<VideoData> currentFrame;
+    if (mReader->mVideoQueue.GetSize() > 0) {
+      VideoData* frame = mReader->mVideoQueue.PeekFront();
+      while (clock_time >= frame->mTime) {
+        mVideoFrameEndTime = frame->mEndTime;
+        currentFrame = frame;
+        mReader->mVideoQueue.PopFront();
+        // Notify the decode thread that the video queue's buffers may have
+        // free'd up space for more frames.
+        mDecoder->GetReentrantMonitor().NotifyAll();
+        mDecoder->UpdatePlaybackOffset(frame->mOffset);
+        if (mReader->mVideoQueue.GetSize() == 0)
+          break;
+        frame = mReader->mVideoQueue.PeekFront();
+      }
+      // Current frame has already been presented, wait until it's time to
+      // present the next frame.
+      if (frame && !currentFrame) {
+        PRInt64 now = IsPlaying()
+          ? (DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration)
+          : mPlayDuration;
+        remainingTime = frame->mTime - mStartTime - now;
+      }
+    }
 
-  // Skip frames up to the frame at the playback position, and figure out
-  // the time remaining until it's time to display the next frame.
-  PRInt64 remainingTime = AUDIO_DURATION_USECS;
-  NS_ASSERTION(clock_time >= mStartTime, "Should have positive clock time.");
-  nsAutoPtr<VideoData> currentFrame;
-  if (mReader->mVideoQueue.GetSize() > 0) {
-    VideoData* frame = mReader->mVideoQueue.PeekFront();
-    while (clock_time >= frame->mTime) {
-      mVideoFrameEndTime = frame->mEndTime;
-      currentFrame = frame;
-      mReader->mVideoQueue.PopFront();
-      // Notify the decode thread that the video queue's buffers may have
-      // free'd up space for more frames.
+    // Check to see if we don't have enough data to play up to the next frame.
+    // If we don't, switch to buffering mode.
+    nsMediaStream* stream = mDecoder->GetCurrentStream();
+    if (mState == DECODER_STATE_DECODING &&
+        mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING &&
+        HasLowDecodedData(remainingTime + EXHAUSTED_DATA_MARGIN_USECS) &&
+        !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+        !stream->IsSuspended() &&
+        (JustExitedQuickBuffering() || HasLowUndecodedData()))
+    {
+      if (currentFrame) {
+        mReader->mVideoQueue.PushFront(currentFrame.forget());
+      }
+      StartBuffering();
+      return;
+    }
+
+    // We've got enough data to keep playing until at least the next frame.
+    // Start playing now if need be.
+    if (!IsPlaying()) {
+      StartPlayback();
       mDecoder->GetReentrantMonitor().NotifyAll();
-      mDecoder->UpdatePlaybackOffset(frame->mOffset);
-      if (mReader->mVideoQueue.GetSize() == 0)
-        break;
-      frame = mReader->mVideoQueue.PeekFront();
+    }
+
+    if (currentFrame) {
+      // Decode one frame and display it.
+      TimeStamp presTime = mPlayStartTime - UsecsToDuration(mPlayDuration) +
+                           UsecsToDuration(currentFrame->mTime - mStartTime);
+      NS_ASSERTION(currentFrame->mTime >= mStartTime, "Should have positive frame time");
+      {
+        ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+        // If we have video, we want to increment the clock in steps of the frame
+        // duration.
+        RenderVideoFrame(currentFrame, presTime);
+      }
+      mDecoder->GetFrameStatistics().NotifyPresentedFrame();
+      PRInt64 now = DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration;
+      remainingTime = currentFrame->mEndTime - mStartTime - now;
+      currentFrame = nsnull;
     }
-    // Current frame has already been presented, wait until it's time to
-    // present the next frame.
-    if (frame && !currentFrame) {
-      PRInt64 now = IsPlaying()
-        ? (DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration)
-        : mPlayDuration;
-      remainingTime = frame->mTime - mStartTime - now;
+
+    // Cap the current time to the larger of the audio and video end time.
+    // This ensures that if we're running off the system clock, we don't
+    // advance the clock to after the media end time.
+    if (mVideoFrameEndTime != -1 || mAudioEndTime != -1) {
+      // These will be non -1 if we've displayed a video frame, or played an audio sample.
+      clock_time = NS_MIN(clock_time, NS_MAX(mVideoFrameEndTime, mAudioEndTime));
+      if (clock_time > GetMediaTime()) {
+        // Only update the playback position if the clock time is greater
+        // than the previous playback position. The audio clock can
+        // sometimes report a time less than its previously reported in
+        // some situations, and we need to gracefully handle that.
+        UpdatePlaybackPosition(clock_time);
+      }
+    }
+
+    // If the number of audio/video samples queued has changed, either by
+    // this function popping and playing a video sample, or by the audio
+    // thread popping and playing an audio sample, we may need to update our
+    // ready state. Post an update to do so.
+    UpdateReadyState();
+
+    if (remainingTime > 0) {
+      Wait(remainingTime);
+    }
+  } else {
+    if (IsPlaying()) {
+      StopPlayback(AUDIO_PAUSE);
+      mDecoder->GetReentrantMonitor().NotifyAll();
+    }
+
+    if (mState == DECODER_STATE_DECODING ||
+        mState == DECODER_STATE_COMPLETED) {
+      mDecoder->GetReentrantMonitor().Wait();
     }
   }
-
-  // Check to see if we don't have enough data to play up to the next frame.
-  // If we don't, switch to buffering mode.
-  nsMediaStream* stream = mDecoder->GetCurrentStream();
-  if (mState == DECODER_STATE_DECODING &&
-      mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING &&
-      HasLowDecodedData(remainingTime + EXHAUSTED_DATA_MARGIN_USECS) &&
-      !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
-      !stream->IsSuspended() &&
-      (JustExitedQuickBuffering() || HasLowUndecodedData()))
-  {
-    if (currentFrame) {
-      mReader->mVideoQueue.PushFront(currentFrame.forget());
-    }
-    StartBuffering();
-    ScheduleStateMachine();
-    return;
-  }
-
-  // We've got enough data to keep playing until at least the next frame.
-  // Start playing now if need be.
-  if (!IsPlaying()) {
-    StartPlayback();
-  }
-
-  if (currentFrame) {
-    // Decode one frame and display it.
-    TimeStamp presTime = mPlayStartTime - UsecsToDuration(mPlayDuration) +
-                          UsecsToDuration(currentFrame->mTime - mStartTime);
-    NS_ASSERTION(currentFrame->mTime >= mStartTime, "Should have positive frame time");
-    {
-      ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-      // If we have video, we want to increment the clock in steps of the frame
-      // duration.
-      RenderVideoFrame(currentFrame, presTime);
-    }
-    mDecoder->GetFrameStatistics().NotifyPresentedFrame();
-    PRInt64 now = DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration;
-    remainingTime = currentFrame->mEndTime - mStartTime - now;
-    currentFrame = nsnull;
-  }
-
-  // Cap the current time to the larger of the audio and video end time.
-  // This ensures that if we're running off the system clock, we don't
-  // advance the clock to after the media end time.
-  if (mVideoFrameEndTime != -1 || mAudioEndTime != -1) {
-    // These will be non -1 if we've displayed a video frame, or played an audio sample.
-    clock_time = NS_MIN(clock_time, NS_MAX(mVideoFrameEndTime, mAudioEndTime));
-    if (clock_time > GetMediaTime()) {
-      // Only update the playback position if the clock time is greater
-      // than the previous playback position. The audio clock can
-      // sometimes report a time less than its previously reported in
-      // some situations, and we need to gracefully handle that.
-      UpdatePlaybackPosition(clock_time);
-    }
-  }
-
-  // If the number of audio/video samples queued has changed, either by
-  // this function popping and playing a video sample, or by the audio
-  // thread popping and playing an audio sample, we may need to update our
-  // ready state. Post an update to do so.
-  UpdateReadyState();
-
-  ScheduleStateMachine(remainingTime);
 }
 
 void nsBuiltinDecoderStateMachine::Wait(PRInt64 aUsecs) {
-  NS_ASSERTION(OnAudioThread(), "Only call on the audio thread");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   TimeStamp end = TimeStamp::Now() + UsecsToDuration(NS_MAX<PRInt64>(USECS_PER_MS, aUsecs));
   TimeStamp now;
   while ((now = TimeStamp::Now()) < end &&
          mState != DECODER_STATE_SHUTDOWN &&
-         mState != DECODER_STATE_SEEKING &&
-         !mStopAudioThread &&
-         IsPlaying())
+         mState != DECODER_STATE_SEEKING)
   {
     PRInt64 ms = static_cast<PRInt64>(NS_round((end - now).ToSeconds() * 1000));
     if (ms == 0 || ms > PR_UINT32_MAX) {
       break;
     }
     mDecoder->GetReentrantMonitor().Wait(PR_MillisecondsToInterval(static_cast<PRUint32>(ms)));
   }
 }
 
 VideoData* nsBuiltinDecoderStateMachine::FindStartTime()
 {
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   PRInt64 startTime = 0;
   mStartTime = 0;
   VideoData* v = nsnull;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     v = mReader->FindStartTime(startTime);
   }
@@ -1744,17 +1567,17 @@ VideoData* nsBuiltinDecoderStateMachine:
       // duration.
       mEndTime = mStartTime + mEndTime;
     }
   }
   // Set the audio start time to be start of media. If this lies before the
   // first acutal audio sample we have, we'll inject silence during playback
   // to ensure the audio starts at the correct time.
   mAudioStartTime = mStartTime;
-  LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder.get(), mStartTime));
+  LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder, mStartTime));
   return v;
 }
 
 void nsBuiltinDecoderStateMachine::UpdateReadyState() {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   nsCOMPtr<nsIRunnable> event;
   switch (GetNextFrameStatus()) {
@@ -1769,31 +1592,53 @@ void nsBuiltinDecoderStateMachine::Updat
       break;
     default:
       PR_NOT_REACHED("unhandled frame state");
   }
 
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
 }
 
+void nsBuiltinDecoderStateMachine::LoadMetadata()
+{
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+
+  LOG(PR_LOG_DEBUG, ("%p Loading Media Headers", mDecoder));
+  nsresult res;
+  nsVideoInfo info;
+  {
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    res = mReader->ReadMetadata(&info);
+  }
+  mInfo = info;
+
+  if (NS_FAILED(res) || (!info.mHasVideo && !info.mHasAudio)) {
+    mState = DECODER_STATE_SHUTDOWN;      
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DecodeError);
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    return;
+  }
+  mDecoder->StartProgressUpdates();
+  mGotDurationFromMetaData = (GetDuration() != -1);
+}
+
 PRBool nsBuiltinDecoderStateMachine::JustExitedQuickBuffering()
 {
   return !mDecodeStartTime.IsNull() &&
     mQuickBuffering &&
     (TimeStamp::Now() - mDecodeStartTime) < TimeDuration::FromSeconds(QUICK_BUFFER_THRESHOLD_USECS);
 }
 
 void nsBuiltinDecoderStateMachine::StartBuffering()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
-  if (IsPlaying()) {
-    StopPlayback();
-  }
-
   TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
   // Go into quick buffering mode provided we've not just left buffering using
   // a "quick exit". This stops us flip-flopping between playing and buffering
   // when the download speed is similar to the decode speed.
   mQuickBuffering =
     !JustExitedQuickBuffering() &&
     decodeDuration < UsecsToDuration(QUICK_BUFFER_THRESHOLD_USECS);
   mBufferingStart = TimeStamp::Now();
@@ -1805,154 +1650,24 @@ void nsBuiltinDecoderStateMachine::Start
   // received" notifications, that mean we're not actually still
   // buffering by the time this runnable executes. So instead
   // we just trigger UpdateReadyStateForData; when it runs, it
   // will check the current state and decide whether to tell
   // the element we're buffering or not.
   UpdateReadyState();
   mState = DECODER_STATE_BUFFERING;
   LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs",
-                     mDecoder.get(), decodeDuration.ToSeconds()));
+                     mDecoder, decodeDuration.ToSeconds()));
   nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
   LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
-    mDecoder.get(),
+    mDecoder,
     stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
     stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)"));
 }
 
 nsresult nsBuiltinDecoderStateMachine::GetBuffered(nsTimeRanges* aBuffered) {
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
   stream->Pin();
   nsresult res = mReader->GetBuffered(aBuffered, mStartTime);
   stream->Unpin();
   return res;
 }
-
-PRBool nsBuiltinDecoderStateMachine::IsPausedAndDecoderWaiting() {
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-
-  return
-    mDecodeThreadWaiting &&
-    mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING &&
-    (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING);
-}
-
-nsresult nsBuiltinDecoderStateMachine::Run()
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-
-  return CallRunStateMachine();
-}
-
-nsresult nsBuiltinDecoderStateMachine::CallRunStateMachine()
-{
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-  // This will be set to PR_TRUE by ScheduleStateMachine() if it's called
-  // while we're in RunStateMachine().
-  mRunAgain = PR_FALSE;
-
-  // Set to PR_TRUE whenever we dispatch an event to run this state machine.
-  // This flag prevents us from dispatching
-  mDispatchedRunEvent = PR_FALSE;
-
-  mTimeout = TimeStamp();
-
-  mIsRunning = PR_TRUE;
-  nsresult res = RunStateMachine();
-  mIsRunning = PR_FALSE;
-
-  if (mRunAgain && !mDispatchedRunEvent) {
-    mDispatchedRunEvent = PR_TRUE;
-    return NS_DispatchToCurrentThread(this);
-  }
-
-  return res;
-}
-
-static void TimeoutExpired(nsITimer *aTimer, void *aClosure) {
-  nsBuiltinDecoderStateMachine *machine =
-    static_cast<nsBuiltinDecoderStateMachine*>(aClosure);
-  NS_ASSERTION(machine, "Must have been passed state machine");
-  machine->TimeoutExpired();
-}
-
-void nsBuiltinDecoderStateMachine::TimeoutExpired()
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  NS_ASSERTION(OnStateMachineThread(), "Must be on state machine thread");
-  if (mIsRunning) {
-    mRunAgain = PR_TRUE;
-  } else if (!mDispatchedRunEvent) {
-    // We don't have an event dispatched to run the state machine, so we
-    // can just run it from here.
-    CallRunStateMachine();
-  }
-  // Otherwise, an event has already been dispatched to run the state machine
-  // as soon as possible. Nothing else needed to do, the state machine is
-  // going to run anyway.
-}
-
-nsresult nsBuiltinDecoderStateMachine::ScheduleStateMachine() {
-  return ScheduleStateMachine(0);
-}
-
-nsresult nsBuiltinDecoderStateMachine::ScheduleStateMachine(PRInt64 aUsecs) {
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-  NS_ABORT_IF_FALSE(gStateMachineThread,
-    "Must have a state machine thread to schedule");
-
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    return NS_ERROR_FAILURE;
-  }
-  aUsecs = PR_MAX(aUsecs, 0);
-
-  TimeStamp timeout = TimeStamp::Now() + UsecsToDuration(aUsecs);
-  if (!mTimeout.IsNull()) {
-    if (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;
-    }
-    if (mTimer) {
-      // We've been asked to schedule a timer to run before an existing timer.
-      // Cancel the existing timer.
-      mTimer->Cancel();
-    }
-  }
-
-  PRUint32 ms = static_cast<PRUint32>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
-  if (ms == 0) {
-    if (mIsRunning) {
-      // We're currently running this state machine on the state machine
-      // thread. Signal it to run again once it finishes its current cycle.
-      mRunAgain = PR_TRUE;
-      return NS_OK;
-    } else if (!mDispatchedRunEvent) {
-      // We're not currently running this state machine on the state machine
-      // thread. Dispatch an event to run one cycle of the state machine.
-      mDispatchedRunEvent = PR_TRUE;
-      return gStateMachineThread->Dispatch(this, NS_DISPATCH_NORMAL);
-    }
-    // We're not currently running this state machine on the state machine
-    // thread, but something has already dispatched an event to run it again,
-    // so just exit; it's going to run real soon.
-    return NS_OK;
-  }
-
-  mTimeout = timeout;
-
-  nsresult res;
-  if (!mTimer) {
-    mTimer = do_CreateInstance("@mozilla.org/timer;1", &res);
-    if (NS_FAILED(res)) return res;
-    mTimer->SetTarget(gStateMachineThread);
-  }
-
-  res = mTimer->InitWithFuncCallback(::TimeoutExpired,
-                                     this,
-                                     ms,
-                                     nsITimer::TYPE_ONE_SHOT);
-  return res;
-}
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -32,106 +32,113 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 /*
-Each video element for a media file has two threads:
+Each video element for a media file has two additional threads beyond
+those needed by nsBuiltinDecoder.
 
   1) The Audio thread writes the decoded audio data to the audio
-     hardware. This is done in a separate thread to ensure that the
+     hardware.  This is done in a seperate thread to ensure that the
      audio hardware gets a constant stream of data without
      interruption due to decoding or display. At some point
      libsydneyaudio will be refactored to have a callback interface
      where it asks for data and an extra thread will no longer be
      needed.
 
   2) The decode thread. This thread reads from the media stream and
-     decodes the Theora and Vorbis data. It places the decoded data into
-     queues for the other threads to pull from.
+     decodes the Theora and Vorbis data. It places the decoded data in
+     a queue for the other threads to pull from.
 
-All file reads, seeks, and all decoding must occur on the decode thread.
-Synchronisation of state between the thread is done via a monitor owned
-by nsBuiltinDecoder.
+All file reads and seeks must occur on either the state machine thread
+or the decode thread. Synchronisation is done via a monitor owned by
+nsBuiltinDecoder.
 
-The lifetime of the decode and audio threads is controlled by the state
-machine when it runs on the shared state machine thread. When playback
-needs to occur they are created and events dispatched to them to run
-them. These events exit when decoding/audio playback is completed or
-no longer required.
+The decode thread and the audio thread are created and destroyed in
+the state machine thread. When playback needs to occur they are
+created and events dispatched to them to start them. These events exit
+when decoding is completed or no longer required (during seeking or
+shutdown).
+    
+The decode thread has its own monitor to ensure that its internal
+state is independent of the other threads, and to ensure that it's not
+hogging the nsBuiltinDecoder monitor while decoding.
 
-A/V synchronisation is handled by the state machine. It examines the audio
-playback time and compares this to the next frame in the queue of video
-frames. If it is time to play the video frame it is then displayed, otherwise
-it schedules the state machine to run again at the time of the next frame.
+a/v synchronisation is handled by the state machine thread. It
+examines the audio playback time and compares this to the next frame
+in the queue of frames. If it is time to play the video frame it is
+then displayed.
 
 Frame skipping is done in the following ways:
 
-  1) The state machine will skip all frames in the video queue whose
+  1) The state machine thread will skip all frames in the video queue whose
      display time is less than the current audio time. This ensures
      the correct frame for the current time is always displayed.
 
   2) The decode thread will stop decoding interframes and read to the
      next keyframe if it determines that decoding the remaining
      interframes will cause playback issues. It detects this by:
        a) If the amount of audio data in the audio queue drops
           below a threshold whereby audio may start to skip.
        b) If the video queue drops below a threshold where it
           will be decoding video data that won't be displayed due
           to the decode thread dropping the frame immediately.
 
-When hardware accelerated graphics is not available, YCbCr conversion
-is done on the decode thread when video frames are decoded.
+YCbCr conversion is done on the decode thread when it is time to display
+the video frame. This means frames that are skipped will not have the
+YCbCr conversion done, improving playback.
 
 The decode thread pushes decoded audio and videos frames into two
 separate queues - one for audio and one for video. These are kept
 separate to make it easy to constantly feed audio data to the sound
 hardware while allowing frame skipping of video data. These queues are
-threadsafe, and neither the decode, audio, or state machine should
+threadsafe, and neither the decode, audio, or state machine thread should
 be able to monopolize them, and cause starvation of the other threads.
 
 Both queues are bounded by a maximum size. When this size is reached
 the decode thread will no longer decode video or audio depending on the
-queue that has reached the threshold. If both queues are full, the decode
-thread will wait on the decoder monitor.
-
-When the decode queues are full (they've reaced their maximum size) and
-the decoder is not in PLAYING play state, the state machine may opt
-to shut down the decode thread in order to conserve resources.
+queue that has reached the threshold.
 
 During playback the audio thread will be idle (via a Wait() on the
-monitor) if the audio queue is empty. Otherwise it constantly pops
-sound data off the queue and plays it with a blocking write to the audio
+monitor) if the audio queue is empty. Otherwise it constantly pops an
+item off the queue and plays it with a blocking write to the audio
 hardware (via nsAudioStream and libsydneyaudio).
 
+The decode thread idles if the video queue is empty or if it is
+not yet time to display the next frame.
 */
 #if !defined(nsBuiltinDecoderStateMachine_h__)
 #define nsBuiltinDecoderStateMachine_h__
 
 #include "prmem.h"
 #include "nsThreadUtils.h"
 #include "nsBuiltinDecoder.h"
 #include "nsBuiltinDecoderReader.h"
 #include "nsAudioAvailableEventManager.h"
 #include "nsHTMLMediaElement.h"
 #include "mozilla/ReentrantMonitor.h"
-#include "nsITimer.h"
 
 /*
-  The state machine class. This manages the decoding and seeking in the
-  nsBuiltinDecoderReader on the decode thread, and A/V sync on the shared
+  The playback state machine class. This manages the decoding in the
+  nsBuiltinDecoderReader on the decode thread, seeking and in-sync-playback on the
   state machine thread, and controls the audio "push" thread.
 
-  All internal state is synchronised via the decoder monitor. State changes
-  are either propagated by NotifyAll on the monitor (typically when state
-  changes need to be propagated to non-state machine threads) or by scheduling
-  the state machine to run another cycle on the shared state machine thread.
+  All internal state is synchronised via the decoder monitor. NotifyAll
+  on the monitor is called when the state of the state machine is changed
+  by the main thread. The following changes to state cause a notify:
+
+    mState and data related to that state changed (mSeekTime, etc)
+    Metadata Loaded
+    First Frame Loaded
+    Frame decoded
+    data pushed or popped from the video and audio queues
 
   See nsBuiltinDecoder.h for more details.
 */
 class nsBuiltinDecoderStateMachine : public nsDecoderStateMachine
 {
 public:
   typedef mozilla::ReentrantMonitor ReentrantMonitor;
   typedef mozilla::TimeStamp TimeStamp;
@@ -160,17 +167,23 @@ public:
   virtual void Play();
   virtual void Seek(double aTime);
   virtual double GetCurrentTime() const;
   virtual void ClearPositionChangeFlag();
   virtual void SetSeekable(PRBool aSeekable);
   virtual void UpdatePlaybackPosition(PRInt64 aTime);
   virtual void StartBuffering();
 
-  // State machine thread run function. Defers to RunStateMachine().
+
+  // Load metadata Called on the state machine thread. The decoder monitor must be held with
+  // exactly one lock count.
+  virtual void LoadMetadata();
+
+  // State machine thread run function. Polls the state, sends frames to be
+  // displayed at appropriate times, and generally manages the decode.
   NS_IMETHOD Run();
 
   // This is called on the state machine thread and audio thread.
   // The decoder monitor must be obtained before calling this.
   PRBool HasAudio() const {
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
     return mInfo.mHasAudio;
   }
@@ -196,39 +209,36 @@ public:
   PRBool IsSeeking() const {
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
     return mState == nsBuiltinDecoderStateMachine::DECODER_STATE_SEEKING;
   }
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
-  PRBool OnAudioThread() const {
+  PRBool OnAudioThread() {
     return IsCurrentThread(mAudioThread);
   }
 
-  PRBool OnStateMachineThread() const {
-    return IsCurrentThread(GetStateMachineThread());
+  PRBool OnStateMachineThread() {
+    return mDecoder->OnStateMachineThread();
   }
- 
-  // The decoder object that created this state machine. The state machine
-  // holds a strong reference to the decoder to ensure that the decoder stays
-  // alive once media element has started the decoder shutdown process, and has
-  // 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<nsBuiltinDecoder> mDecoder;
+
+  // Decode loop, called on the decode thread.
+  void DecodeLoop();
+
+  // The decoder object that created this state machine. The decoder
+  // always outlives us since it controls our lifetime. This is accessed
+  // read only on the AV, state machine, audio and main thread.
+  nsBuiltinDecoder* mDecoder;
 
   // The decoder monitor must be obtained before modifying this state.
-  // NotifyAll on the monitor must be called when the state is changed so
-  // that interested threads can wake up and alter behaviour if appropriate
-  // Accessed on state machine, audio, main, and AV thread.
+  // NotifyAll on the monitor must be called when the state is changed by
+  // the main thread so the decoder thread can wake up.
+  // Accessed on state machine, audio, main, and AV thread. 
   State mState;
 
   nsresult GetBuffered(nsTimeRanges* aBuffered);
 
   void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
@@ -242,33 +252,16 @@ public:
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
     return mSeekable;
   }
 
   // Sets the current frame buffer length for the MozAudioAvailable event.
   // Accessed on the main and state machine threads.
   virtual void SetFrameBufferLength(PRUint32 aLength);
 
-  // Returns the shared state machine thread.
-  static nsIThread* GetStateMachineThread();
-
-  // Schedules the shared state machine thread to run the state machine.
-  // If the state machine thread is the currently running the state machine,
-  // we wait until that has completely finished before running the state
-  // machine again.
-  nsresult ScheduleStateMachine();
-
-  // 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(PRInt64 aUsecs);
-
-  // Timer function to implement ScheduleStateMachine(aUsecs).
-  void TimeoutExpired();
-
 protected:
 
   // Returns PR_TRUE if we've got less than aAudioUsecs microseconds of decoded
   // and playable data. The decoder monitor must be held.
   PRBool HasLowDecodedData(PRInt64 aAudioUsecs) const;
 
   // Returns PR_TRUE if we're running low on data which is not yet decoded.
   // The decoder monitor must be held.
@@ -292,24 +285,24 @@ protected:
   PRBool JustExitedQuickBuffering();
 
   // Waits on the decoder ReentrantMonitor for aUsecs microseconds. If the decoder
   // monitor is awoken by a Notify() call, we'll continue waiting, unless
   // we've moved into shutdown state. This enables us to ensure that we
   // wait for a specified time, and that the myriad of Notify()s we do on
   // the decoder monitor don't cause the audio thread to be starved. aUsecs
   // values of less than 1 millisecond are rounded up to 1 millisecond
-  // (see bug 651023). The decoder monitor must be held. Called only on the
-  // audio thread.
+  // (see bug 651023). The decoder monitor must be held.
   void Wait(PRInt64 aUsecs);
 
   // Dispatches an asynchronous event to update the media element's ready state.
   void UpdateReadyState();
 
-  // Resets playback timing data. Called when we seek, on the decode thread.
+  // Resets playback timing data. Called when we seek, on the state machine
+  // thread.
   void ResetPlayback();
 
   // Returns the audio clock, if we have audio, or -1 if we don't.
   // Called on the state machine thread.
   PRInt64 GetAudioClock();
 
   // Returns the presentation time of the first sample or frame in the media.
   // If the media has video, it returns the first video frame. The decoder
@@ -318,18 +311,19 @@ protected:
   VideoData* FindStartTime();
 
   // Update only the state machine's current playback position (and duration,
   // if unknown).  Does not update the playback position on the decoder or
   // media element -- use UpdatePlaybackPosition for that.  Called on the state
   // machine thread, caller must hold the decoder lock.
   void UpdatePlaybackPositionInternal(PRInt64 aTime);
 
-  // Pushes the image down the rendering pipeline. Called on the shared state
-  // machine thread. The decoder monitor must *not* be held when calling this.
+  // Performs YCbCr to RGB conversion, and pushes the image down the
+  // rendering pipeline. Called on the state machine thread. The decoder
+  // monitor must not be held when calling this.
   void RenderVideoFrame(VideoData* aData, TimeStamp aTarget);
  
   // If we have video, display a video frame if it's time for display has
   // arrived, otherwise sleep until it's time for the next sample. Update
   // the current frame time as appropriate, and trigger ready state update.
   // The decoder monitor must be held with exactly one lock count. Called
   // on the state machine thread.
   void AdvanceFrame();
@@ -345,45 +339,46 @@ protected:
                        PRUint32 aChannels,
                        PRUint64 aSampleOffset);
 
   // Pops an audio chunk from the front of the audio queue, and pushes its
   // sound data to the audio hardware. MozAudioAvailable sample data is also
   // queued here. Called on the audio thread.
   PRUint32 PlayFromAudioQueue(PRUint64 aSampleOffset, PRUint32 aChannels);
 
-  // Stops the decode thread. The decoder monitor must be held with exactly
+  // Stops the decode threads. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
-  void StopDecodeThread();
-
-  // Stops the audio thread. The decoder monitor must be held with exactly
-  // one lock count. Called on the state machine thread.
-  void StopAudioThread();
+  void StopDecodeThreads();
 
-  // Starts the decode thread. The decoder monitor must be held with exactly
+  // Starts the decode threads. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
-  nsresult StartDecodeThread();
-
-  // Starts the audio thread. The decoder monitor must be held with exactly
-  // one lock count. Called on the state machine thread.
-  nsresult StartAudioThread();
+  nsresult StartDecodeThreads();
 
   // The main loop for the audio thread. Sent to the thread as
   // an nsRunnableMethod. This continually does blocking writes to
   // to audio stream to play audio data.
   void AudioLoop();
 
-  // Sets internal state which causes playback of media to pause.
-  // The decoder monitor must be held. Called on the main, state machine,
-  // and decode threads.
-  void StopPlayback();
+  // Stop or pause playback of media. This has two modes, denoted by
+  // aMode being either AUDIO_PAUSE or AUDIO_SHUTDOWN.
+  //
+  // AUDIO_PAUSE: Suspends the audio stream to be resumed later.
+  // This does not close the OS based audio stream 
+  //
+  // AUDIO_SHUTDOWN: Closes and destroys the audio stream and
+  // releases any OS resources.
+  //
+  // The decoder monitor must be held with exactly one lock count. Called
+  // on the state machine thread.
+  enum eStopMode {AUDIO_PAUSE, AUDIO_SHUTDOWN};
+  void StopPlayback(eStopMode aMode);
 
-  // Sets internal state which causes playback of media to begin or resume.
-  // Must be called with the decode monitor held. Called on the state machine
-  // and decode threads.
+  // Resume playback of media. Must be called with the decode monitor held.
+  // This resumes a paused audio stream. The decoder monitor must be held with
+  // exactly one lock count. Called on the state machine thread.
   void StartPlayback();
 
   // Moves the decoder into decoding state. Called on the state machine
   // thread. The decoder monitor must be held.
   void StartDecoding();
 
   // Returns PR_TRUE if we're currently playing. The decoder monitor must
   // be held.
@@ -400,76 +395,39 @@ protected:
   }
 
   // Returns an upper bound on the number of microseconds of audio that is
   // decoded and playable. This is the sum of the number of usecs of audio which
   // is decoded and in the reader's audio queue, and the usecs of unplayed audio
   // which has been pushed to the audio hardware for playback. Note that after
   // calling this, the audio hardware may play some of the audio pushed to
   // hardware, so this can only be used as a upper bound. The decoder monitor
-  // must be held when calling this. Called on the decode thread.
+  // must be held when calling this. Called on the decoder thread.
   PRInt64 GetDecodedAudioDuration();
 
-  // Load metadata. Called on the decode thread. The decoder monitor
-  // must be held with exactly one lock count.
-  nsresult DecodeMetadata();
-
-  // Seeks to mSeekTarget. Called on the decode thread. The decoder monitor
-  // must be held with exactly one lock count.
-  void DecodeSeek();
-
-  // Decode loop, decodes data until EOF or shutdown.
-  // Called on the decode thread.
-  void DecodeLoop();
-
-  // Decode thread run function. Determines which of the Decode*() functions
-  // to call.
-  void DecodeThreadRun();
-
-  // 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();
-
-  PRBool IsStateMachineScheduled() const {
-    mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-    return !mTimeout.IsNull() || mRunAgain;
-  }
-
-  // Returns PR_TRUE if we're not playing and the decode thread has filled its
-  // decode buffers and is waiting. We can shut the decode thread down in this
-  // case as it may not be needed again.
-  PRBool IsPausedAndDecoderWaiting();
+  // ReentrantMonitor on mAudioStream. This monitor must be held in
+  // order to delete or use the audio stream. This stops us destroying
+  // the audio stream while it's being used on another thread
+  // (typically when it's being written to on the audio thread).
+  ReentrantMonitor mAudioReentrantMonitor;
 
   // The size of the decoded YCbCr frame.
   // Accessed on state machine thread.
   PRUint32 mCbCrSize;
 
   // Accessed on state machine thread.
   nsAutoArrayPtr<unsigned char> mCbCrBuffer;
 
   // 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;
 
-  // 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.
-  TimeStamp mTimeout;
-
   // The time that playback started from the system clock. This is used for
   // timing the presentation of video frames when there's no audio.
   // Accessed only via the state machine thread.
   TimeStamp mPlayStartTime;
 
   // The amount of time we've spent playing already the media. The current
   // playback position is therefore |Now() - mPlayStartTime +
   // mPlayDuration|, which must be adjusted by mStartTime if used with media
@@ -478,34 +436,33 @@ protected:
 
   // Time that buffering started. Used for buffering timeout and only
   // accessed on the state machine thread. This is null while we're not
   // buffering.
   TimeStamp mBufferingStart;
 
   // Start time of the media, in microseconds. This is the presentation
   // time of the first sample decoded from the media, and is used to calculate
-  // duration and as a bounds for seeking. Accessed on state machine, decode,
-  // and main threads. Access controlled by decoder monitor.
+  // duration and as a bounds for seeking. Accessed on state machine and
+  // main thread. Access controlled by decoder monitor.
   PRInt64 mStartTime;
 
   // Time of the last page in the media, in microseconds. This is the
   // end time of the last sample in the media. Accessed on state
-  // machine, decode, and main threads. Access controlled by decoder monitor.
+  // machine and main thread. Access controlled by decoder monitor.
   PRInt64 mEndTime;
 
   // Position to seek to in microseconds when the seek state transition occurs.
   // The decoder monitor lock must be obtained before reading or writing
-  // this value. Accessed on main and decode thread.
+  // this value. Accessed on main and state machine thread.
   PRInt64 mSeekTime;
 
-  // The audio stream resource. Used on the state machine, and audio threads.
-  // This is created and destroyed on the audio thread, while holding the
-  // decoder monitor, so if this is used off the audio thread, you must
-  // first acquire the decoder monitor and check that it is non-null.
+  // The audio stream resource. Used on the state machine, audio, and
+  // main threads. You must hold the mAudioReentrantMonitor, and must
+  // NOT hold the decoder monitor when using the audio stream!
   nsRefPtr<nsAudioStream> mAudioStream;
 
   // The reader, don't call its methods with the decoder monitor held.
   // This is created in the play state machine's constructor, and destroyed
   // in the play state machine's destructor.
   nsAutoPtr<nsBuiltinDecoderReader> mReader;
 
   // The time of the current frame in microseconds. This is referenced from
@@ -554,58 +511,27 @@ protected:
   // destroyed. Written by the audio playback thread and read and written by
   // the state machine thread. Synchronised via decoder monitor.
   PRPackedBool mAudioCompleted;
 
   // PR_TRUE if mDuration has a value obtained from an HTTP header, or from
   // the media index/metadata. Accessed on the state machine thread.
   PRPackedBool mGotDurationFromMetaData;
     
-  // PR_FALSE while decode thread should be running. Accessed state machine
-  // and decode threads. Syncrhonised by decoder monitor.
-  PRPackedBool mStopDecodeThread;
-
-  // PR_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.
-  PRPackedBool mDecodeThreadIdle;
-
-  // PR_FALSE while audio thread should be running. Accessed state machine
-  // and audio threads. Syncrhonised by decoder monitor.
-  PRPackedBool mStopAudioThread;
+  // PR_FALSE while decode threads should be running. Accessed on audio, 
+  // state machine and decode threads. Syncrhonised by decoder monitor.
+  PRPackedBool mStopDecodeThreads;
 
   // If this is PR_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
   // ran fast enough to exhaust all data while the download is starting up.
   // Synchronised via decoder monitor.
   PRPackedBool mQuickBuffering;
 
-  // PR_TRUE if the shared state machine thread is currently running this
-  // state machine.
-  PRPackedBool mIsRunning;
-
-  // PR_TRUE if we should run the state machine again once the current
-  // state machine run has finished.
-  PRPackedBool mRunAgain;
-
-  // PR_TRUE if we've dispatched an event to run the state machine. It's
-  // imperative that we don't dispatch multiple events to run the state
-  // machine at the same time, as our code assume all events are synchronous.
-  // If we dispatch multiple events, the second event can run while the
-  // first is shutting down a thread, causing inconsistent state.
-  PRPackedBool mDispatchedRunEvent;
-
-  // PR_TRUE if the decode thread has gone filled its buffers and is now
-  // waiting to be awakened before it continues decoding. Synchronized
-  // by the decoder monitor.
-  PRPackedBool mDecodeThreadWaiting;
-
 private:
   // 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.
   nsAudioAvailableEventManager mEventManager;
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -60,16 +60,30 @@ class nsTimeRanges;
 // for example, 44100 with 2 channels, 2*1024 = 2048.
 #define FRAMEBUFFER_LENGTH_PER_CHANNEL 1024
 
 // The total size of the framebuffer used for MozAudioAvailable events
 // has to be within the following range.
 #define FRAMEBUFFER_LENGTH_MIN 512
 #define FRAMEBUFFER_LENGTH_MAX 16384
 
+// Shuts down a thread asynchronously.
+class ShutdownThreadEvent : public nsRunnable 
+{
+public:
+  ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
+  ~ShutdownThreadEvent() {}
+  NS_IMETHOD Run() {
+    mThread->Shutdown();
+    return NS_OK;
+  }
+private:
+  nsCOMPtr<nsIThread> mThread;
+};
+
 // All methods of nsMediaDecoder must be called from the main thread only
 // with the exception of GetImageContainer, SetVideoData and GetStatistics,
 // which can be called from any thread.
 class nsMediaDecoder : public nsIObserver
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
--- a/content/media/ogg/nsOggReader.cpp
+++ b/content/media/ogg/nsOggReader.cpp
@@ -135,30 +135,33 @@ nsresult nsOggReader::Init(nsBuiltinDeco
   }
   int ret = ogg_sync_init(&mOggState);
   NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 nsresult nsOggReader::ResetDecode()
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   nsresult res = NS_OK;
 
   if (NS_FAILED(nsBuiltinDecoderReader::ResetDecode())) {
     res = NS_ERROR_FAILURE;
   }
 
-  // Discard any previously buffered packets/pages.
-  ogg_sync_reset(&mOggState);
-  if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
-    res = NS_ERROR_FAILURE;
-  }
-  if (mTheoraState && NS_FAILED(mTheoraState->Reset())) {
-    res = NS_ERROR_FAILURE;
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    // Discard any previously buffered packets/pages.
+    ogg_sync_reset(&mOggState);
+    if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
+      res = NS_ERROR_FAILURE;
+    }
+    if (mTheoraState && NS_FAILED(mTheoraState->Reset())) {
+      res = NS_ERROR_FAILURE;
+    }
   }
 
   return res;
 }
 
 PRBool nsOggReader::ReadHeaders(nsOggCodecState* aState)
 {
   while (!aState->DoneReadingHeaders()) {
@@ -170,17 +173,18 @@ PRBool nsOggReader::ReadHeaders(nsOggCod
       aState->DecodeHeader(packet);
     }
   }
   return aState->Init();
 }
 
 nsresult nsOggReader::ReadMetadata(nsVideoInfo* aInfo)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on play state machine thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
   // We read packets until all bitstreams have read all their header packets.
   // We record the offset of the first non-header page so that we know
   // what page to seek to when seeking to the media start.
 
   ogg_page page;
   nsAutoTArray<nsOggCodecState*,4> bitstreams;
   PRBool readAllBOS = PR_FALSE;
@@ -309,25 +313,27 @@ nsresult nsOggReader::ReadMetadata(nsVid
       if (HasVideo()) {
         tracks.AppendElement(mTheoraState->mSerial);
       }
       if (HasAudio()) {
         tracks.AppendElement(mVorbisState->mSerial);
       }
       PRInt64 duration = 0;
       if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
-        ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+        ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+        ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
         mDecoder->GetStateMachine()->SetDuration(duration);
         LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration));
       }
     }
   }
 
   {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+    ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
 
     nsMediaStream* stream = mDecoder->GetCurrentStream();
     if (mDecoder->GetStateMachine()->GetDuration() == -1 &&
         mDecoder->GetStateMachine()->GetState() != nsDecoderStateMachine::DECODER_STATE_SHUTDOWN &&
         stream->GetLength() >= 0 &&
         mDecoder->GetStateMachine()->GetSeekable())
     {
       // We didn't get a duration from the index or a Content-Duration header.
@@ -391,17 +397,19 @@ nsresult nsOggReader::DecodeVorbis(ogg_p
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
 PRBool nsOggReader::DecodeAudioData()
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on playback or decode thread.");
   NS_ASSERTION(mVorbisState!=0, "Need Vorbis state to decode audio");
 
   // Read the next data packet. Skip any non-data packets we encounter.
   ogg_packet* packet = 0;
   do {
     if (packet) {
       nsOggCodecState::ReleasePacket(packet);
     }
@@ -467,16 +475,19 @@ nsresult nsOggReader::DecodeTheora(ogg_p
     VideoData::YCbCrBuffer b;
     for (PRUint32 i=0; i < 3; ++i) {
       b.mPlanes[i].mData = buffer[i].data;
       b.mPlanes[i].mHeight = buffer[i].height;
       b.mPlanes[i].mWidth = buffer[i].width;
       b.mPlanes[i].mStride = buffer[i].stride;
     }
 
+    // Need the monitor to be held to be able to use mInfo. This
+    // is held by our caller.
+    mReentrantMonitor.AssertCurrentThreadIn();
     VideoData *v = VideoData::Create(mInfo,
                                      mDecoder->GetImageContainer(),
                                      mPageOffset,
                                      time,
                                      endTime,
                                      b,
                                      isKeyframe,
                                      aPacket->granulepos,
@@ -490,17 +501,19 @@ nsresult nsOggReader::DecodeTheora(ogg_p
     mVideoQueue.Push(v);
   }
   return NS_OK;
 }
 
 PRBool nsOggReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
                                      PRInt64 aTimeThreshold)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine or AV thread.");
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   PRUint32 parsed = 0, decoded = 0;
   nsMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
   // Read the next data packet. Skip any non-data packets we encounter.
   ogg_packet* packet = 0;
@@ -539,17 +552,19 @@ PRBool nsOggReader::DecodeVideoFrame(PRB
     return PR_FALSE;
   }
 
   return PR_TRUE;
 }
 
 PRInt64 nsOggReader::ReadOggPage(ogg_page* aPage)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on play state machine or decode thread.");
+  mReentrantMonitor.AssertCurrentThreadIn();
 
   int ret = 0;
   while((ret = ogg_sync_pageseek(&mOggState, aPage)) <= 0) {
     if (ret < 0) {
       // Lost page sync, have to skip up to next page.
       mPageOffset += -ret;
       continue;
     }
@@ -577,17 +592,19 @@ PRInt64 nsOggReader::ReadOggPage(ogg_pag
   PRInt64 offset = mPageOffset;
   mPageOffset += aPage->header_len + aPage->body_len;
   
   return offset;
 }
 
 ogg_packet* nsOggReader::NextOggPacket(nsOggCodecState* aCodecState)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on play state machine or decode thread.");
+  mReentrantMonitor.AssertCurrentThreadIn();
 
   if (!aCodecState || !aCodecState->mActive) {
     return nsnull;
   }
 
   ogg_packet* packet;
   while ((packet = aCodecState->PacketOut()) == nsnull) {
     // The codec state does not have any buffered pages, so try to read another
@@ -620,17 +637,18 @@ GetChecksum(ogg_page* page)
                (p[1] << 8) + 
                (p[2] << 16) +
                (p[3] << 24);
   return c;
 }
 
 PRInt64 nsOggReader::RangeStartTime(PRInt64 aOffset)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, nsnull);
   nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   NS_ENSURE_SUCCESS(res, nsnull);
   PRInt64 startTime = 0;
   nsBuiltinDecoderReader::FindStartTime(startTime);
   return startTime;
 }
@@ -642,18 +660,19 @@ struct nsAutoOggSyncState {
   ~nsAutoOggSyncState() {
     ogg_sync_clear(&mState);
   }
   ogg_sync_state mState;
 };
 
 PRInt64 nsOggReader::RangeEndTime(PRInt64 aEndOffset)
 {
-  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
-               "Should be on state machine or decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
 
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, -1);
   PRInt64 position = stream->Tell();
   PRInt64 endTime = RangeEndTime(0, aEndOffset, PR_FALSE);
   nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, position);
   NS_ENSURE_SUCCESS(res, -1);
   return endTime;
@@ -773,17 +792,19 @@ PRInt64 nsOggReader::RangeEndTime(PRInt6
     }
   }
 
   return endTime;
 }
 
 nsresult nsOggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
+  mReentrantMonitor.AssertCurrentThreadIn();
   nsTArray<nsByteRange> cached;
   nsresult res = mDecoder->GetCurrentStream()->GetCachedRanges(cached);
   NS_ENSURE_SUCCESS(res, res);
 
   for (PRUint32 index = 0; index < cached.Length(); index++) {
     nsByteRange& range = cached[index];
     PRInt64 startTime = -1;
     PRInt64 endTime = -1;
@@ -812,17 +833,18 @@ nsresult nsOggReader::GetSeekRanges(nsTA
 
 nsOggReader::SeekRange
 nsOggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
                              PRInt64 aTarget,
                              PRInt64 aStartTime,
                              PRInt64 aEndTime,
                              PRBool aExact)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
   PRInt64 so = 0;
   PRInt64 eo = mDecoder->GetCurrentStream()->GetLength();
   PRInt64 st = aStartTime;
   PRInt64 et = aEndTime;
   for (PRUint32 i = 0; i < ranges.Length(); i++) {
     const SeekRange &r = ranges[i];
     if (r.mTimeStart < aTarget) {
       so = r.mOffsetStart;
@@ -950,17 +972,18 @@ nsresult nsOggReader::SeekInBufferedRang
 
   // We have an active Theora bitstream. Decode the next Theora frame, and
   // extract its keyframe's time.
   PRBool eof;
   do {
     PRBool skip = PR_FALSE;
     eof = !DecodeVideoFrame(skip, 0);
     {
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+      ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
         return NS_ERROR_FAILURE;
       }
     }
   } while (!eof &&
            mVideoQueue.GetSize() == 0);
 
   VideoData* video = mVideoQueue.PeekFront();
@@ -1024,17 +1047,19 @@ nsresult nsOggReader::SeekInUnbuffered(P
   return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
 }
 
 nsresult nsOggReader::Seek(PRInt64 aTarget,
                            PRInt64 aStartTime,
                            PRInt64 aEndTime,
                            PRInt64 aCurrentTime)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
   nsresult res;
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, NS_ERROR_FAILURE);
 
   if (aTarget == aStartTime) {
     // We've seeked to the media start. Just seek to the offset of the first
     // content page.
@@ -1042,17 +1067,18 @@ nsresult nsOggReader::Seek(PRInt64 aTarg
     NS_ENSURE_SUCCESS(res,res);
 
     mPageOffset = 0;
     res = ResetDecode();
     NS_ENSURE_SUCCESS(res,res);
 
     NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
     {
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+      ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       mDecoder->UpdatePlaybackPosition(aStartTime);
     }
   } else if (CanDecodeToTarget(aTarget, aCurrentTime)) {
     LOG(PR_LOG_DEBUG, ("%p Seek target (%lld) is close to current time (%lld), "
         "will just decode to it", mDecoder, aCurrentTime, aTarget));
   } else {
     IndexedSeekResult sres = SeekToKeyframeUsingIndex(aTarget);
     NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE);
@@ -1152,17 +1178,18 @@ PageSync(nsMediaStream* aStream,
   
   return PAGE_SYNC_OK;
 }
 
 nsresult nsOggReader::SeekBisection(PRInt64 aTarget,
                                     const SeekRange& aRange,
                                     PRUint32 aFuzz)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
   nsresult res;
   nsMediaStream* stream = mDecoder->GetCurrentStream();
 
   if (aTarget == aRange.mTimeStart) {
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
     res = stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
--- a/content/media/ogg/nsOggReader.h
+++ b/content/media/ogg/nsOggReader.h
@@ -66,31 +66,37 @@ public:
   virtual PRBool DecodeAudioData();
 
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.  
   virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
                                   PRInt64 aTimeThreshold);
 
-  virtual PRBool HasAudio() {
+  virtual PRBool HasAudio()
+  {
+    mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mVorbisState != 0 && mVorbisState->mActive;
   }
 
-  virtual PRBool HasVideo() {
+  virtual PRBool HasVideo()
+  {
+    mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mTheoraState != 0 && mTheoraState->mActive;
   }
 
   virtual nsresult ReadMetadata(nsVideoInfo* aInfo);
   virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime);
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime);
 
 private:
 
-  PRBool HasSkeleton() {
+  PRBool HasSkeleton()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mSkeletonState != 0 && mSkeletonState->mActive;
   }
 
   // Returns PR_TRUE if we should decode up to the seek target rather than
   // seeking to the target using a bisection search or index-assisted seek.
   // We should do this if the seek target (aTarget, in usecs), lies not too far
   // ahead of the current playback position (aCurrentTime, in usecs).
   PRBool CanDecodeToTarget(PRInt64 aTarget,
--- a/content/media/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -118,16 +118,17 @@ var gPlayTests = [
   { name:"seek.yuv", type:"video/x-raw-yuv", duration:1.833 },
   
   // A really short, low sample rate, single channel file. This tests whether
   // we can handle playing files when only push very little audio data to the
   // hardware.
   { name:"spacestorm-1000Hz-100ms.ogg", type:"audio/ogg", duration:0.099 },
 
   { name:"bogus.duh", type:"bogus/duh", duration:Number.NaN }
+  
 ];
 
 // Converts a path/filename to a file:// URI which we can load from disk.
 // Optionally checks whether the file actually exists on disk at the location
 // we've specified.
 function fileUriToSrc(path, mustExist) {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   const Ci = Components.interfaces;
--- a/content/media/test/seek1.js
+++ b/content/media/test/seek1.js
@@ -24,32 +24,31 @@ function startTest() {
   v.currentTime=seekTime;
   seekFlagStart = v.seeking;
   return false;
 }
 
 function seekStarted() {
   if (completed)
     return false;
-  ok(v.currentTime >= seekTime - 0.1,
-     "Video currentTime should be around " + seekTime + ": " + v.currentTime + " (seeking)");
+  ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime);
   v.pause();
   startPassed = true;
   return false;
 }
 
 function seekEnded() {
   if (completed)
     return false;
 
   var t = v.currentTime;
   // Since we were playing, and we only paused asynchronously, we can't be
   // sure that we paused before the seek finished, so we may have played
   // ahead arbitrarily far.
-  ok(t >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + t + " (seeked)");
+  ok(t >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + t);
   v.play();
   endPassed = true;
   seekFlagEnd = v.seeking;
   return false;
 }
 
 function playbackEnded() {
   if (completed)
--- a/content/media/test/test_buffered.html
+++ b/content/media/test/test_buffered.html
@@ -36,43 +36,42 @@ function ended(e) {
 
   // Ensure INDEX_SIZE_ERR is thrown when we access outside the range
   var caught = false;
   try {
     b.start(-1);
   } catch (e) {
     caught = e.code == DOMException.INDEX_SIZE_ERR;
   }
-  is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under start bounds range");
+  is(caught, true, "Should throw INDEX_SIZE_ERR on under start bounds range");
   
   caught = false;
   try {
     b.end(-1);
   } catch (e) {
     caught = e.code == DOMException.INDEX_SIZE_ERR;
   }
-  is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under end bounds range");
+  is(caught, true, "Should throw INDEX_SIZE_ERR on under end bounds range");
 
   caught = false;
   try {
     b.start(b.length);
   } catch (e) {
     caught = e.code == DOMException.INDEX_SIZE_ERR;
   }
-  is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over start bounds range");
+  is(caught, true, "Should throw INDEX_SIZE_ERR on over start bounds range");
   
   caught = false;
   try {
     b.end(b.length);
   } catch (e) {
     caught = e.code == DOMException.INDEX_SIZE_ERR;
   }
-  is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over end bounds range");
-
-  v.src = "";
+  is(caught, true, "Should throw INDEX_SIZE_ERR on over end bounds range");
+  
   v.parentNode.removeChild(v);
   manager.finished(v.token);
 
   return false;
 }
 
 function startTest(test, token) {
   var v = document.createElement('video');
--- a/content/media/test/test_bug493187.html
+++ b/content/media/test/test_bug493187.html
@@ -28,17 +28,16 @@ function startSeeking(e) {
 }
 
 function canPlayThrough(e) {
   var v = e.target;
   if (v._seeked && !v._finished) {
     ok(true, "Got canplaythrough after seek for " + v._name);
     v._finished = true;
     v.parentNode.removeChild(v);
-    v.src = "";
     manager.finished(v.token);
   }
 }
 
 function startTest(test, token) {
   // TODO: Bug 568402, there's a bug in the WAV backend where we sometimes
   // don't send canplaythrough events after seeking. Once that is fixed,
   // we should remove this guard below so that we run this test for audio.
--- a/content/media/wave/nsWaveReader.cpp
+++ b/content/media/wave/nsWaveReader.cpp
@@ -147,41 +147,45 @@ nsWaveReader::~nsWaveReader()
 
 nsresult nsWaveReader::Init(nsBuiltinDecoderReader* aCloneDonor)
 {
   return NS_OK;
 }
 
 nsresult nsWaveReader::ReadMetadata(nsVideoInfo* aInfo)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
   PRBool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset();
   if (!loaded) {
     return NS_ERROR_FAILURE;
   }
 
   mInfo.mHasAudio = PR_TRUE;
   mInfo.mHasVideo = PR_FALSE;
   mInfo.mAudioRate = mSampleRate;
   mInfo.mAudioChannels = mChannels;
 
   *aInfo = mInfo;
 
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+  ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
 
   mDecoder->GetStateMachine()->SetDuration(
     static_cast<PRInt64>(BytesToTime(GetDataLength()) * USECS_PER_S));
 
   return NS_OK;
 }
 
 PRBool nsWaveReader::DecodeAudioData()
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine thread or decode thread.");
 
   PRInt64 pos = GetPosition() - mWavePCMOffset;
   PRInt64 len = GetDataLength();
   PRInt64 remaining = len - pos;
   NS_ASSERTION(remaining >= 0, "Current wave position is greater than wave file length");
 
   static const PRInt64 BLOCK_SIZE = 4096;
   PRInt64 readSize = NS_MIN(BLOCK_SIZE, remaining);
@@ -237,24 +241,28 @@ PRBool nsWaveReader::DecodeAudioData()
                                  mChannels));
 
   return PR_TRUE;
 }
 
 PRBool nsWaveReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
                                       PRInt64 aTimeThreshold)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine or decode thread.");
 
   return PR_FALSE;
 }
 
 nsresult nsWaveReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
   double d = BytesToTime(GetDataLength());
   NS_ASSERTION(d < PR_INT64_MAX / USECS_PER_S, "Duration overflow"); 
   PRInt64 duration = static_cast<PRInt64>(d * USECS_PER_S);
   double seekTime = NS_MIN(aTarget, duration) / static_cast<double>(USECS_PER_S);
--- a/content/media/webm/nsWebMReader.cpp
+++ b/content/media/webm/nsWebMReader.cpp
@@ -205,32 +205,34 @@ void nsWebMReader::Cleanup()
   if (mContext) {
     nestegg_destroy(mContext);
     mContext = nsnull;
   }
 }
 
 nsresult nsWebMReader::ReadMetadata(nsVideoInfo* aInfo)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
   nestegg_io io;
   io.read = webm_read;
   io.seek = webm_seek;
   io.tell = webm_tell;
   io.userdata = static_cast<nsBuiltinDecoder*>(mDecoder);
   int r = nestegg_init(&mContext, io, NULL);
   if (r == -1) {
     return NS_ERROR_FAILURE;
   }
 
   uint64_t duration = 0;
   r = nestegg_duration(mContext, &duration);
   if (r == 0) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+    ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
     mDecoder->GetStateMachine()->SetDuration(duration / NS_PER_USEC);
   }
 
   unsigned int ntracks = 0;
   r = nestegg_track_count(mContext, &ntracks);
   if (r == -1) {
     Cleanup();
     return NS_ERROR_FAILURE;
@@ -405,17 +407,17 @@ ogg_packet nsWebMReader::InitOggPacket(u
   packet.e_o_s = aEOS;
   packet.granulepos = aGranulepos;
   packet.packetno = mPacketCount++;
   return packet;
 }
  
 PRBool nsWebMReader::DecodeAudioPacket(nestegg_packet* aPacket, PRInt64 aOffset)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  mReentrantMonitor.AssertCurrentThreadIn();
 
   int r = 0;
   unsigned int count = 0;
   r = nestegg_packet_count(aPacket, &count);
   if (r == -1) {
     return PR_FALSE;
   }
 
@@ -583,31 +585,34 @@ nsReturnRef<NesteggPacketHolder> nsWebMR
     } while (PR_TRUE);
   }
 
   return holder.out();
 }
 
 PRBool nsWebMReader::DecodeAudioData()
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+    "Should be on state machine thread or decode thread.");
   nsAutoRef<NesteggPacketHolder> holder(NextPacket(AUDIO));
   if (!holder) {
     mAudioQueue.Finish();
     return PR_FALSE;
   }
 
   return DecodeAudioPacket(holder->mPacket, holder->mOffset);
 }
 
 PRBool nsWebMReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
                                       PRInt64 aTimeThreshold)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine or decode thread.");
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   PRUint32 parsed = 0, decoded = 0;
   nsMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
   nsAutoRef<NesteggPacketHolder> holder(NextPacket(VIDEO));
   if (!holder) {
@@ -643,16 +648,17 @@ PRBool nsWebMReader::DecodeVideoFrame(PR
     nsAutoRef<NesteggPacketHolder> next_holder(NextPacket(VIDEO));
     if (next_holder) {
       r = nestegg_packet_tstamp(next_holder->mPacket, &next_tstamp);
       if (r == -1) {
         return PR_FALSE;
       }
       mVideoPackets.PushFront(next_holder.disown());
     } else {
+      ReentrantMonitorAutoExit exitMon(mReentrantMonitor);
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       nsBuiltinDecoderStateMachine* s =
         static_cast<nsBuiltinDecoderStateMachine*>(mDecoder->GetStateMachine());
       PRInt64 endTime = s->GetEndMediaTime();
       if (endTime == -1) {
         return PR_FALSE;
       }
       next_tstamp = endTime * NS_PER_USEC;
@@ -756,18 +762,19 @@ PRBool nsWebMReader::CanDecodeToTarget(P
 {
   return aTarget >= aCurrentTime &&
          aTarget - aCurrentTime < SEEK_DECODE_MARGIN;
 }
 
 nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime,
                             PRInt64 aCurrentTime)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
   if (CanDecodeToTarget(aTarget, aCurrentTime)) {
     LOG(PR_LOG_DEBUG, ("%p Seek target (%lld) is close to current time (%lld), "
                        "will just decode to it", mDecoder, aCurrentTime, aTarget));
   } else {
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
--- a/content/media/webm/nsWebMReader.h
+++ b/content/media/webm/nsWebMReader.h
@@ -138,23 +138,23 @@ public:
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.  
   virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
                                   PRInt64 aTimeThreshold);
 
   virtual PRBool HasAudio()
   {
-    NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+    mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mHasAudio;
   }
 
   virtual PRBool HasVideo()
   {
-    NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+    mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mHasVideo;
   }
 
   virtual nsresult ReadMetadata(nsVideoInfo* aInfo);
   virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime);
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime);
   virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset);