Bug 592833 - Move seeking to the decode thread. r=roc
authorChris Pearce <chris@pearce.org.nz>
Tue, 12 Jul 2011 15:39:25 +1200
changeset 73063 61d23b08b595a95808345e6dc458cac85fb086c7
parent 73062 786f3a0c29b53a67b60834bb1bdc2d69a0b879f0
child 73064 01fc3692c8883999a610472fe774be73149dcb00
push id313
push usereakhgari@mozilla.com
push dateTue, 16 Aug 2011 19:58:41 +0000
treeherdermozilla-aurora@ef9d1c90dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs592833
milestone8.0a1
Bug 592833 - Move seeking to the decode thread. r=roc
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/ogg/nsOggReader.cpp
content/media/test/manifest.js
content/media/wave/nsWaveReader.cpp
content/media/webm/nsWebMReader.cpp
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -187,16 +187,17 @@ nsBuiltinDecoderStateMachine::nsBuiltinD
   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),
   mQuickBuffering(PR_FALSE),
   mEventManager(aDecoder)
 {
   MOZ_COUNT_CTOR(nsBuiltinDecoderStateMachine);
 }
 
 nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
@@ -227,18 +228,46 @@ 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"));
+    }
+    mDecoder->GetReentrantMonitor().NotifyAll();
+  }
+
+  while (mState != DECODER_STATE_SHUTDOWN && mState != DECODER_STATE_COMPLETED) {
+    if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
+      DecodeLoop();
+    } else if (mState == DECODER_STATE_SEEKING) {
+      DecodeSeek();
+      mDecoder->GetReentrantMonitor().NotifyAll();
+    }
+  }
+
+  mDecodeThreadIdle = PR_TRUE;
+  LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder));
+}
+
 void nsBuiltinDecoderStateMachine::DecodeLoop()
 {
+  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
@@ -263,29 +292,20 @@ 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;
 
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  if (mState == DECODER_STATE_DECODING_METADATA) {
-    if (NS_FAILED(DecodeMetadata())) {
-      LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-    }
-    mDecoder->GetReentrantMonitor().NotifyAll();
-  }
-
   // Main decode loop.
   PRBool videoPlaying = HasVideo();
   PRBool audioPlaying = HasAudio();
-  while (mState != DECODER_STATE_SHUTDOWN &&
+  while ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
          !mStopDecodeThread &&
          (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)
@@ -359,17 +379,17 @@ 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_SHUTDOWN &&
+    if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
         !mStopDecodeThread &&
         (videoPlaying || audioPlaying) &&
         (!audioPlaying || (GetDecodedAudioDuration() >= ampleAudioThreshold &&
                            audioQueue.GetSize() > 0))
         &&
         (!videoPlaying ||
           static_cast<PRUint32>(videoQueue.GetSize()) >= AMPLE_VIDEO_FRAMES))
     {
@@ -377,30 +397,30 @@ void nsBuiltinDecoderStateMachine::Decod
       // 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.
-      mon.Wait();
+      mDecoder->GetReentrantMonitor().Wait();
     }
 
   } // End decode loop.
 
   if (!mStopDecodeThread &&
       mState != DECODER_STATE_SHUTDOWN &&
       mState != DECODER_STATE_SEEKING)
   {
     mState = DECODER_STATE_COMPLETED;
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
 
-  LOG(PR_LOG_DEBUG, ("%p Shutting down DecodeLoop this=%p", mDecoder, this));
+  LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder));
 }
 
 PRBool nsBuiltinDecoderStateMachine::IsPlaying()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   return !mPlayStartTime.IsNull();
 }
@@ -671,17 +691,17 @@ nsresult nsBuiltinDecoderStateMachine::I
   if (aCloneDonor) {
     cloneReader = static_cast<nsBuiltinDecoderStateMachine*>(aCloneDonor)->mReader;
   }
   return mReader->Init(cloneReader);
 }
 
 void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
 {
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "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
@@ -703,17 +723,17 @@ void nsBuiltinDecoderStateMachine::StopP
         mEventManager.Clear();
       }
     }
   }
 }
 
 void nsBuiltinDecoderStateMachine::StartPlayback()
 {
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "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));
   mDecoder->mPlaybackStatistics.Start(TimeStamp::Now());
   if (HasAudio()) {
     PRInt32 rate = mInfo.mAudioRate;
     PRInt32 channels = mInfo.mAudioChannels;
@@ -734,17 +754,17 @@ void nsBuiltinDecoderStateMachine::Start
     }
   }
   mPlayStartTime = TimeStamp::Now();
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void nsBuiltinDecoderStateMachine::UpdatePlaybackPositionInternal(PRInt64 aTime)
 {
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "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(),
@@ -887,18 +907,17 @@ void nsBuiltinDecoderStateMachine::Play(
     mState = DECODER_STATE_DECODING;
     mDecodeStartTime = TimeStamp::Now();
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
 }
 
 void nsBuiltinDecoderStateMachine::ResetPlayback()
 {
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
-               "Should be on state machine thread.");
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   mVideoFrameEndTime = -1;
   mAudioStartTime = -1;
   mAudioEndTime = -1;
   mAudioCompleted = PR_FALSE;
 }
 
 void nsBuiltinDecoderStateMachine::Seek(double aTime)
 {
@@ -926,34 +945,35 @@ void nsBuiltinDecoderStateMachine::Seek(
   mSeekTime = NS_MIN(mSeekTime, mEndTime);
   mSeekTime = NS_MAX(mStartTime, mSeekTime);
   LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder, aTime));
   mState = DECODER_STATE_SEEKING;
 }
 
 void nsBuiltinDecoderStateMachine::StopDecodeThread()
 {
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   mStopDecodeThread = PR_TRUE;
   mDecoder->GetReentrantMonitor().NotifyAll();
   if (mDecodeThread) {
     LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder));
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       mDecodeThread->Shutdown();
     }
     mDecodeThread = nsnull;
+    mDecodeThreadIdle = PR_FALSE;
   }
 }
 
 void nsBuiltinDecoderStateMachine::StopAudioThread()
 {
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   mStopAudioThread = PR_TRUE;
   mDecoder->GetReentrantMonitor().NotifyAll();
   if (mAudioThread) {
     LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder));
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -965,26 +985,30 @@ void nsBuiltinDecoderStateMachine::StopA
 
 nsresult
 nsBuiltinDecoderStateMachine::StartDecodeThread()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   mStopDecodeThread = PR_FALSE;
-  if (!mDecodeThread && mState < DECODER_STATE_COMPLETED) {
+  if ((mDecodeThread && !mDecodeThreadIdle) || mState >= DECODER_STATE_COMPLETED)
+    return NS_OK;
+
+  if (!mDecodeThread) {
     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(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
@@ -1074,16 +1098,18 @@ void nsBuiltinDecoderStateMachine::SetFr
   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));
   nsresult res;
   nsVideoInfo info;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     res = mReader->ReadMetadata(&info);
   }
@@ -1136,16 +1162,129 @@ nsresult nsBuiltinDecoderStateMachine::D
   if (mState == DECODER_STATE_DECODING_METADATA) {
     LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
     StartDecoding();
   }
 
   return NS_OK;
 }
 
+void nsBuiltinDecoderStateMachine::DecodeSeek()
+{
+  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(AUDIO_SHUTDOWN);
+    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 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)
+    return;
+
+  // Try to decode another frame to detect if we're at the end...
+  LOG(PR_LOG_DEBUG, ("Seek completed, mCurrentFrameTime=%lld\n", 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, 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;
+}
+
 nsresult nsBuiltinDecoderStateMachine::Run()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
 
   while (PR_TRUE) {
@@ -1193,121 +1332,25 @@ nsresult nsBuiltinDecoderStateMachine::R
 
         if (mState != DECODER_STATE_DECODING)
           continue;
       }
       break;
 
     case DECODER_STATE_SEEKING:
       {
-        // 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);
+        // Ensure decode thread is alive and well...
+        if (NS_FAILED(StartDecodeThread())) {
+          continue;
         }
 
-        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);
-          StopDecodeThread();
-          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 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);
-              }
-            }
-          }
+        // Wait until seeking finishes...
+        while (mState == DECODER_STATE_SEEKING) {
+          mon.Wait();
         }
-        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));
-
-        // 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();
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -212,19 +212,16 @@ public:
   PRBool OnAudioThread() const {
     return IsCurrentThread(mAudioThread);
   }
 
   PRBool OnStateMachineThread() const {
     return mDecoder->OnStateMachineThread();
   }
 
-  // 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 by
   // the main thread so the decoder thread can wake up.
@@ -286,18 +283,17 @@ protected:
   // 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.
   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 state machine
-  // thread.
+  // Resets playback timing data. Called when we seek, on the decode 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
@@ -405,16 +401,28 @@ protected:
   // hardware, so this can only be used as a upper bound. The decoder monitor
   // 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();
+
   // 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.
@@ -522,16 +530,23 @@ protected:
   // 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;
 
   // 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.
--- a/content/media/ogg/nsOggReader.cpp
+++ b/content/media/ogg/nsOggReader.cpp
@@ -398,18 +398,17 @@ nsresult nsOggReader::DecodeVorbis(ogg_p
     }
   }
   return NS_OK;
 }
 
 PRBool nsOggReader::DecodeAudioData()
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
-               "Should be on playback or decode thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on 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);
     }
@@ -502,18 +501,17 @@ nsresult nsOggReader::DecodeTheora(ogg_p
   }
   return NS_OK;
 }
 
 PRBool nsOggReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
                                      PRInt64 aTimeThreshold)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
-               "Should be on state machine or AV thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on 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);
 
   // Read the next data packet. Skip any non-data packets we encounter.
   ogg_packet* packet = 0;
@@ -552,18 +550,17 @@ PRBool nsOggReader::DecodeVideoFrame(PRB
     return PR_FALSE;
   }
 
   return PR_TRUE;
 }
 
 PRInt64 nsOggReader::ReadOggPage(ogg_page* aPage)
 {
-  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
-               "Should be on play state machine or decode thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on 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;
@@ -592,18 +589,17 @@ 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->OnStateMachineThread() || mDecoder->OnDecodeThread(),
-               "Should be on play state machine or decode thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   mReentrantMonitor.AssertCurrentThreadIn();
 
   if (!aCodecState || !aCodecState->mActive) {
     return nsnull;
   }
 
   ogg_packet* packet;
   while ((packet = aCodecState->PacketOut()) == nsnull) {
@@ -792,18 +788,17 @@ PRInt64 nsOggReader::RangeEndTime(PRInt6
     }
   }
 
   return endTime;
 }
 
 nsresult nsOggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges)
 {
-  NS_ASSERTION(mDecoder->OnStateMachineThread(),
-               "Should be on state machine thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode 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;
@@ -833,18 +828,17 @@ nsresult nsOggReader::GetSeekRanges(nsTA
 
 nsOggReader::SeekRange
 nsOggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
                              PRInt64 aTarget,
                              PRInt64 aStartTime,
                              PRInt64 aEndTime,
                              PRBool aExact)
 {
-  NS_ASSERTION(mDecoder->OnStateMachineThread(),
-               "Should be on state machine thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode 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;
@@ -1048,18 +1042,17 @@ nsresult nsOggReader::SeekInUnbuffered(P
 }
 
 nsresult nsOggReader::Seek(PRInt64 aTarget,
                            PRInt64 aStartTime,
                            PRInt64 aEndTime,
                            PRInt64 aCurrentTime)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  NS_ASSERTION(mDecoder->OnStateMachineThread(),
-               "Should be on state machine thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode 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.
@@ -1178,18 +1171,17 @@ PageSync(nsMediaStream* aStream,
   
   return PAGE_SYNC_OK;
 }
 
 nsresult nsOggReader::SeekBisection(PRInt64 aTarget,
                                     const SeekRange& aRange,
                                     PRUint32 aFuzz)
 {
-  NS_ASSERTION(mDecoder->OnStateMachineThread(),
-               "Should be on state machine thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode 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/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -118,17 +118,16 @@ 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/wave/nsWaveReader.cpp
+++ b/content/media/wave/nsWaveReader.cpp
@@ -251,18 +251,17 @@ PRBool nsWaveReader::DecodeVideoFrame(PR
                "Should be on state machine or decode thread.");
 
   return PR_FALSE;
 }
 
 nsresult nsWaveReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  NS_ASSERTION(mDecoder->OnStateMachineThread(),
-               "Should be on state machine thread.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode 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
@@ -762,17 +762,17 @@ PRBool nsWebMReader::CanDecodeToTarget(P
   return aTarget >= aCurrentTime &&
          aTarget - aCurrentTime < SEEK_DECODE_MARGIN;
 }
 
 nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime,
                             PRInt64 aCurrentTime)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+  NS_ASSERTION(mDecoder->OnDecodeThread(),
                "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;