Bug 566779 - Clean up media start and end time calculation. r=doublec
authorChris Pearce <chris@pearce.org.nz>
Mon, 09 May 2011 09:10:28 +1200
changeset 69117 a4bf69cf2f78fe4e1cfd1bc903499c8fc90469b1
parent 69116 11339a517f7962a5c8f25cee890fb0b4dd261734
child 69118 615cffa67cfaef8c9ee695bfaa5ec3eaa1bc5c02
push idunknown
push userunknown
push dateunknown
reviewersdoublec
bugs566779
milestone6.0a1
Bug 566779 - Clean up media start and end time calculation. r=doublec
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderReader.cpp
content/media/nsBuiltinDecoderReader.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/ogg/nsOggDecoderStateMachine.cpp
content/media/ogg/nsOggDecoderStateMachine.h
content/media/ogg/nsOggReader.cpp
content/media/ogg/nsOggReader.h
content/media/raw/nsRawReader.cpp
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -255,16 +255,21 @@ public:
 
   // Called from the main thread to set the duration of the media resource
   // if it is able to be obtained via HTTP headers. Called from the 
   // state machine thread to set the duration if it is obtained from the
   // media metadata. The decoder monitor must be obtained before calling this.
   // aDuration is in microseconds.
   virtual void SetDuration(PRInt64 aDuration) = 0;
 
+  // Called while decoding metadata to set the end time of the media
+  // 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;
 
   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
@@ -280,19 +285,25 @@ public:
   virtual double GetCurrentTime() const = 0;
 
   // Clear the flag indicating that a playback position change event
   // is currently queued. This is called from the main thread and must
   // be called with the decode monitor held.
   virtual void ClearPositionChangeFlag() = 0;
 
   // Called from the main thread to set whether the media resource can
-  // be seeked. The decoder monitor must be obtained before calling this.
+  // seek into unbuffered ranges. The decoder monitor must be obtained
+  // before calling this.
   virtual void SetSeekable(PRBool aSeekable) = 0;
 
+  // Returns PR_TRUE if the media resource can seek into unbuffered ranges,
+  // as set by SetSeekable(). The decoder monitor must be obtained before
+  // calling this.
+  virtual PRBool GetSeekable() = 0;
+
   // Update the playback position. This can result in a timeupdate event
   // and an invalidate of the frame being dispatched asynchronously if
   // there is no such event currently queued.
   // Only called on the decoder thread. Must be called with
   // the decode monitor held.
   virtual void UpdatePlaybackPosition(PRInt64 aTime) = 0;
 
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
--- a/content/media/nsBuiltinDecoderReader.cpp
+++ b/content/media/nsBuiltinDecoderReader.cpp
@@ -224,25 +224,20 @@ nsresult nsBuiltinDecoderReader::ResetDe
   nsresult res = NS_OK;
 
   mVideoQueue.Reset();
   mAudioQueue.Reset();
 
   return res;
 }
 
-VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64 aOffset,
-                                                 PRInt64& aOutStartTime)
+VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64& aOutStartTime)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
 
-  if (NS_FAILED(ResetDecode())) {
-    return nsnull;
-  }
-
   // 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()) {
     videoData = DecodeToFirstData(&nsBuiltinDecoderReader::DecodeVideoFrame,
@@ -262,21 +257,16 @@ VideoData* nsBuiltinDecoderReader::FindS
   PRInt64 startTime = PR_MIN(videoStartTime, audioStartTime);
   if (startTime != PR_INT64_MAX) {
     aOutStartTime = startTime;
   }
 
   return videoData;
 }
 
-PRInt64 nsBuiltinDecoderReader::FindEndTime(PRInt64 aEndOffset)
-{
-  return -1;
-}
-
 template<class Data>
 Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
                                                 MediaQueue<Data>& aQueue)
 {
   PRBool eof = PR_FALSE;
   while (!eof && aQueue.GetSize() == 0) {
     {
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -439,24 +439,19 @@ public:
   virtual PRBool HasVideo() = 0;
 
   // Read header data for all bitstreams in the file. Fills mInfo with
   // the data required to present the media. Returns NS_OK on success,
   // or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(nsVideoInfo* aInfo) = 0;
 
   // Stores the presentation time of the first frame/sample we'd be
-  // able to play if we started playback at aOffset, and returns the
-  // first video sample, if we have video.
-  virtual VideoData* FindStartTime(PRInt64 aOffset,
-                                   PRInt64& aOutStartTime);
-
-  // Returns the end time of the last page which occurs before aEndOffset.
-  // This will not read past aEndOffset. Returns -1 on failure. 
-  virtual PRInt64 FindEndTime(PRInt64 aEndOffset);
+  // able to play if we started playback at the current position. Returns
+  // the first video sample, if we have video.
+  VideoData* FindStartTime(PRInt64& aOutStartTime);
 
   // 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;
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -812,24 +812,36 @@ PRInt64 nsBuiltinDecoderStateMachine::Ge
 }
 
 void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
 {
   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(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.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   mSeekable = aSeekable;
 }
 
@@ -1554,17 +1566,17 @@ VideoData* nsBuiltinDecoderStateMachine:
 {
   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(0, startTime);
+    v = mReader->FindStartTime(startTime);
   }
   if (startTime != 0) {
     mStartTime = startTime;
     if (mGotDurationFromMetaData) {
       NS_ASSERTION(mEndTime != -1,
                    "We should have mEndTime as supplied duration here");
       // We were specified a duration from a Content-Duration HTTP header.
       // Adjust mEndTime so that mEndTime-mStartTime matches the specified
@@ -1575,40 +1587,16 @@ VideoData* nsBuiltinDecoderStateMachine:
   // 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, mStartTime));
   return v;
 }
 
-void nsBuiltinDecoderStateMachine::FindEndTime() 
-{
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
-
-  nsMediaStream* stream = mDecoder->GetCurrentStream();
-
-  // Seek to the end of file to find the length and duration.
-  PRInt64 length = stream->GetLength();
-  NS_ASSERTION(length > 0, "Must have a content length to get end time");
-
-  mEndTime = 0;
-  PRInt64 endTime = 0;
-  {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    endTime = mReader->FindEndTime(length);
-  }
-  if (endTime != -1) {
-    mEndTime = endTime;
-  }
-
-  LOG(PR_LOG_DEBUG, ("%p Media end time is %lld", mDecoder, mEndTime));   
-}
-
 void nsBuiltinDecoderStateMachine::UpdateReadyState() {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   nsCOMPtr<nsIRunnable> event;
   switch (GetNextFrameStatus()) {
     case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING:
       event = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::NextFrameUnavailableBuffering);
       break;
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -153,16 +153,17 @@ public:
   { 
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
     return mState; 
   }
   virtual void SetVolume(double aVolume);
   virtual void Shutdown();
   virtual PRInt64 GetDuration();
   virtual void SetDuration(PRInt64 aDuration);
+  void SetEndTime(PRInt64 aEndTime);
   virtual PRBool OnDecodeThread() const {
     return IsCurrentThread(mDecodeThread);
   }
 
   virtual nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
   virtual void Play();
   virtual void Seek(double aTime);
   virtual double GetCurrentTime() const;
@@ -242,16 +243,21 @@ public:
     mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
 
   PRInt64 GetEndMediaTime() const {
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
     return mEndTime;
   }
 
+  PRBool GetSeekable() {
+    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);
 
 protected:
 
   // Returns PR_TRUE if we've got less than aAudioUsecs microseconds of decoded
   // and playable data. The decoder monitor must be held.
@@ -298,21 +304,16 @@ protected:
   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
   // monitor must be held with exactly one lock count. Called on the state
   // machine thread.
   VideoData* FindStartTime();
 
-  // Finds the end time of the last frame of data in the file, storing the value
-  // in mEndTime if successful. The decoder must be held with exactly one lock
-  // count. Called on the state machine thread.
-  void FindEndTime();
-
   // 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);
 
   // Performs YCbCr to RGB conversion, and pushes the image down the
   // rendering pipeline. Called on the state machine thread. The decoder
--- a/content/media/ogg/nsOggDecoderStateMachine.cpp
+++ b/content/media/ogg/nsOggDecoderStateMachine.cpp
@@ -40,29 +40,8 @@
 #include "nsOggDecoderStateMachine.h"
 #include "nsOggReader.h"
 #include "nsOggDecoder.h"
 
 nsOggDecoderStateMachine::nsOggDecoderStateMachine(nsBuiltinDecoder* aDecoder) :
   nsBuiltinDecoderStateMachine(aDecoder, new nsOggReader(aDecoder))
 {
 }
-
-void nsOggDecoderStateMachine::LoadMetadata()
-{
-  nsBuiltinDecoderStateMachine::LoadMetadata();
-
-  // Get the duration from the media file. We only do this if the
-  // content length of the resource is known as we need to seek
-  // to the end of the file to get the last time field. We also
-  // only do this if the resource is seekable and if we haven't
-  // already obtained the duration via an HTTP header.
-
-  if (mState != DECODER_STATE_SHUTDOWN &&
-      mDecoder->GetCurrentStream()->GetLength() >= 0 &&
-      mSeekable &&
-      mEndTime == -1)
-  {
-    mDecoder->StopProgressUpdates();
-    FindEndTime();
-    mDecoder->StartProgressUpdates();
-  }
-}
--- a/content/media/ogg/nsOggDecoderStateMachine.h
+++ b/content/media/ogg/nsOggDecoderStateMachine.h
@@ -40,15 +40,11 @@
 #define nsOggDecoderStateMachine_h_
 
 #include "nsBuiltinDecoderStateMachine.h"
 
 class nsOggDecoderStateMachine : public nsBuiltinDecoderStateMachine
 {
 public:
   nsOggDecoderStateMachine(nsBuiltinDecoder* aDecoder);
-
-  // Overload LoadMetadata to seek to the end of the file and get the
-  // duration.
-  virtual void LoadMetadata();
 };
 
 #endif
--- a/content/media/ogg/nsOggReader.cpp
+++ b/content/media/ogg/nsOggReader.cpp
@@ -256,23 +256,23 @@ nsresult nsOggReader::ReadMetadata(nsVid
       s->Deactivate();
     }
   }
 
   if (mTheoraState && ReadHeaders(mTheoraState)) {
     mInfo.mHasVideo = PR_TRUE;
     mInfo.mPixelAspectRatio = mTheoraState->mPixelAspectRatio;
     mInfo.mPicture = nsIntRect(mTheoraState->mInfo.pic_x,
-                                mTheoraState->mInfo.pic_y,
-                                mTheoraState->mInfo.pic_width,
-                                mTheoraState->mInfo.pic_height);
+                               mTheoraState->mInfo.pic_y,
+                               mTheoraState->mInfo.pic_width,
+                               mTheoraState->mInfo.pic_height);
     mInfo.mFrame = nsIntSize(mTheoraState->mInfo.frame_width,
                               mTheoraState->mInfo.frame_height);
     mInfo.mDisplay = nsIntSize(mInfo.mPicture.width,
-                                mInfo.mPicture.height);
+                               mInfo.mPicture.height);
     gfxIntSize sz(mTheoraState->mInfo.pic_width,
                   mTheoraState->mInfo.pic_height);
     mDecoder->SetVideoData(sz,
                            mTheoraState->mPixelAspectRatio,
                            nsnull,
                            TimeStamp::Now());
     // Copy Theora info data for time computations on other threads.
     memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo));
@@ -313,16 +313,42 @@ nsresult nsOggReader::ReadMetadata(nsVid
         ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
         ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
         mDecoder->GetStateMachine()->SetDuration(duration);
         LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration));
       }
     }
   }
 
+  {
+    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.
+      // Seek to the end of file to find the end time.
+      PRInt64 length = stream->GetLength();
+      NS_ASSERTION(length > 0, "Must have a content length to get end time");
+
+      PRInt64 endTime = 0;
+      {
+        ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+        endTime = RangeEndTime(length);
+      }
+      if (endTime != -1) {
+        mDecoder->GetStateMachine()->SetEndTime(endTime);
+        LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
+      }
+    }
+  }
   *aInfo = mInfo;
 
   return NS_OK;
 }
 
 nsresult nsOggReader::DecodeVorbis(ogg_packet* aPacket) {
   NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!");
 
@@ -396,37 +422,16 @@ PRBool nsOggReader::DecodeAudioData()
     // be no more samples.
     mAudioQueue.Finish();
     return PR_FALSE;
   }
 
   return PR_TRUE;
 }
 
-#ifdef DEBUG
-// Ensures that all the VideoData in aFrames array are stored in increasing
-// order by timestamp. Used in assertions in debug builds.
-static PRBool
-AllFrameTimesIncrease(nsTArray<nsAutoPtr<VideoData> >& aFrames)
-{
-  PRInt64 prevTime = -1;
-  PRInt64 prevGranulepos = -1;
-  for (PRUint32 i = 0; i < aFrames.Length(); i++) {
-    VideoData* f = aFrames[i];
-    if (f->mTime < prevTime) {
-      return PR_FALSE;
-    }
-    prevTime = f->mTime;
-    prevGranulepos = f->mTimecode;
-  }
-
-  return PR_TRUE;
-}
-#endif
-
 nsresult nsOggReader::DecodeTheora(ogg_packet* aPacket)
 {
   NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1),
     "Packets must have valid granulepos and packetno");
 
   int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
   if (ret != 0 && ret != TH_DUPFRAME) {
     return NS_ERROR_FAILURE;
@@ -615,89 +620,100 @@ GetChecksum(ogg_page* page)
   const unsigned char* p = page->header + 22;
   PRUint32 c =  p[0] +
                (p[1] << 8) + 
                (p[2] << 16) +
                (p[3] << 24);
   return c;
 }
 
-VideoData* nsOggReader::FindStartTime(PRInt64 aOffset,
-                                      PRInt64& aOutStartTime)
+PRInt64 nsOggReader::RangeStartTime(PRInt64 aOffset)
 {
   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);
-  return nsBuiltinDecoderReader::FindStartTime(aOffset, aOutStartTime);
+  PRInt64 startTime = 0;
+  nsBuiltinDecoderReader::FindStartTime(startTime);
+  return startTime;
 }
 
-PRInt64 nsOggReader::FindEndTime(PRInt64 aEndOffset)
+struct nsAutoOggSyncState {
+  nsAutoOggSyncState() {
+    ogg_sync_init(&mState);
+  }
+  ~nsAutoOggSyncState() {
+    ogg_sync_clear(&mState);
+  }
+  ogg_sync_state mState;
+};
+
+PRInt64 nsOggReader::RangeEndTime(PRInt64 aEndOffset)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
-  PRInt64 endTime = FindEndTime(0, aEndOffset, PR_FALSE, &mOggState);
-  // Reset read head to start of media data.
+
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, -1);
-  nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+  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;
 }
 
-PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
-                                 PRInt64 aEndOffset,
-                                 PRBool aCachedDataOnly,
-                                 ogg_sync_state* aState)
+PRInt64 nsOggReader::RangeEndTime(PRInt64 aStartOffset,
+                                  PRInt64 aEndOffset,
+                                  PRBool aCachedDataOnly)
 {
   nsMediaStream* stream = mDecoder->GetCurrentStream();
-  ogg_sync_reset(aState);
+  nsAutoOggSyncState sync;
 
   // We need to find the last page which ends before aEndOffset that
   // has a granulepos that we can convert to a timestamp. We do this by
   // backing off from aEndOffset until we encounter a page on which we can
   // interpret the granulepos. If while backing off we encounter a page which
   // we've previously encountered before, we'll either backoff again if we
   // haven't found an end time yet, or return the last end time found.
   const int step = 5000;
   PRInt64 readStartOffset = aEndOffset;
   PRInt64 readHead = aEndOffset;
   PRInt64 endTime = -1;
   PRUint32 checksumAfterSeek = 0;
   PRUint32 prevChecksumAfterSeek = 0;
   PRBool mustBackOff = PR_FALSE;
   while (PR_TRUE) {
     ogg_page page;    
-    int ret = ogg_sync_pageseek(aState, &page);
+    int ret = ogg_sync_pageseek(&sync.mState, &page);
     if (ret == 0) {
       // We need more data if we've not encountered a page we've seen before,
       // or we've read to the end of file.
       if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
         if (endTime != -1 || readStartOffset == 0) {
           // We have encountered a page before, or we're at the end of file.
           break;
         }
         mustBackOff = PR_FALSE;
         prevChecksumAfterSeek = checksumAfterSeek;
         checksumAfterSeek = 0;
-        ogg_sync_reset(aState);
+        ogg_sync_reset(&sync.mState);
         readStartOffset = NS_MAX(static_cast<PRInt64>(0), readStartOffset - step);
         readHead = NS_MAX(aStartOffset, readStartOffset);
       }
 
       PRInt64 limit = NS_MIN(static_cast<PRInt64>(PR_UINT32_MAX),
                              aEndOffset - readHead);
       limit = NS_MAX(static_cast<PRInt64>(0), limit);
       limit = NS_MIN(limit, static_cast<PRInt64>(step));
       PRUint32 bytesToRead = static_cast<PRUint32>(limit);
       PRUint32 bytesRead = 0;
-      char* buffer = ogg_sync_buffer(aState, bytesToRead);
+      char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
       NS_ASSERTION(buffer, "Must have buffer");
       nsresult res;
       if (aCachedDataOnly) {
         res = stream->ReadFromCache(buffer, readHead, bytesToRead);
         NS_ENSURE_SUCCESS(res, -1);
         bytesRead = bytesToRead;
       } else {
         NS_ASSERTION(readHead < aEndOffset,
@@ -706,17 +722,17 @@ PRInt64 nsOggReader::FindEndTime(PRInt64
         NS_ENSURE_SUCCESS(res, -1);
         res = stream->Read(buffer, bytesToRead, &bytesRead);
         NS_ENSURE_SUCCESS(res, -1);
       }
       readHead += bytesRead;
 
       // Update the synchronisation layer with the number
       // of bytes written to the buffer
-      ret = ogg_sync_wrote(aState, bytesRead);
+      ret = ogg_sync_wrote(&sync.mState, bytesRead);
       if (ret != 0) {
         endTime = -1;
         break;
       }
 
       continue;
     }
 
@@ -756,18 +772,16 @@ PRInt64 nsOggReader::FindEndTime(PRInt64
     }
 
     PRInt64 t = codecState->Time(granulepos);
     if (t != -1) {
       endTime = t;
     }
   }
 
-  ogg_sync_reset(aState);
-
   return endTime;
 }
 
 nsresult nsOggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
   mReentrantMonitor.AssertCurrentThreadIn();
@@ -779,19 +793,19 @@ nsresult nsOggReader::GetSeekRanges(nsTA
     nsByteRange& range = cached[index];
     PRInt64 startTime = -1;
     PRInt64 endTime = -1;
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
     PRInt64 startOffset = range.mStart;
     PRInt64 endOffset = range.mEnd;
-    FindStartTime(startOffset, startTime);
+    startTime = RangeStartTime(startOffset);
     if (startTime != -1 &&
-        ((endTime = FindEndTime(endOffset)) != -1))
+        ((endTime = RangeEndTime(endOffset)) != -1))
     {
       NS_ASSERTION(startTime < endTime,
                    "Start time must be before end time");
       aRanges.AppendElement(SeekRange(startOffset,
                                       endOffset,
                                       startTime,
                                       endTime));
      }
@@ -1418,46 +1432,43 @@ nsresult nsOggReader::GetBuffered(nsTime
   nsresult res = stream->GetCachedRanges(ranges);
   NS_ENSURE_SUCCESS(res, res);
 
   // Traverse across the buffered byte ranges, determining the time ranges
   // they contain. nsMediaStream::GetNextCachedData(offset) returns -1 when
   // offset is after the end of the media stream, or there's no more cached
   // data after the offset. This loop will run until we've checked every
   // buffered range in the media, in increasing order of offset.
-  ogg_sync_state state;
-  ogg_sync_init(&state);
+  nsAutoOggSyncState sync;
   for (PRUint32 index = 0; index < ranges.Length(); index++) {
     // Ensure the offsets are after the header pages.
     PRInt64 startOffset = ranges[index].mStart;
     PRInt64 endOffset = ranges[index].mEnd;
 
     // Because the granulepos time is actually the end time of the page,
     // we special-case (startOffset == 0) so that the first
     // buffered range always appears to be buffered from the media start
     // time, rather than from the end-time of the first page.
     PRInt64 startTime = (startOffset == 0) ? aStartTime : -1;
 
     // Find the start time of the range. Read pages until we find one with a
     // granulepos which we can convert into a timestamp to use as the time of
     // the start of the buffered range.
-    ogg_sync_reset(&state);
+    ogg_sync_reset(&sync.mState);
     while (startTime == -1) {
       ogg_page page;
       PRInt32 discard;
       PageSyncResult res = PageSync(stream,
-                                    &state,
+                                    &sync.mState,
                                     PR_TRUE,
                                     startOffset,
                                     endOffset,
                                     &page,
                                     discard);
       if (res == PAGE_SYNC_ERROR) {
-        // If we don't clear the sync state before exit we'll leak.
-        ogg_sync_clear(&state);
         return NS_ERROR_FAILURE;
       } else if (res == PAGE_SYNC_END_OF_RANGE) {
         // Hit the end of range without reading a page, give up trying to
         // find a start time for this buffered range, skip onto the next one.
         break;
       }
 
       PRInt64 granulepos = ogg_page_granulepos(&page);
@@ -1481,35 +1492,31 @@ nsresult nsOggReader::GetBuffered(nsTime
         // Stream is not the theora or vorbis stream we're playing,
         // but is one that we have header data for.
         startOffset += page.header_len + page.body_len;
         continue;
       }
       else {
         // Page is for a stream we don't know about (possibly a chained
         // ogg), return an error.
-        ogg_sync_clear(&state);
         return PAGE_SYNC_ERROR;
       }
     }
 
     if (startTime != -1) {
       // We were able to find a start time for that range, see if we can
       // find an end time.
-      PRInt64 endTime = FindEndTime(startOffset, endOffset, PR_TRUE, &state);
+      PRInt64 endTime = RangeEndTime(startOffset, endOffset, PR_TRUE);
       if (endTime != -1) {
         aBuffered->Add(startTime / static_cast<double>(USECS_PER_S),
                        (endTime - aStartTime) / static_cast<double>(USECS_PER_S));
       }
     }
   }
 
-  // If we don't clear the sync state before exit we'll leak.
-  ogg_sync_clear(&state);
-
   return NS_OK;
 }
 
 PRBool nsOggReader::IsKnownStream(PRUint32 aSerial)
 {
   for (PRUint32 i = 0; i < mKnownStreams.Length(); i++) {
     PRUint32 serial = mKnownStreams[i];
     if (serial == aSerial) {
--- a/content/media/ogg/nsOggReader.h
+++ b/content/media/ogg/nsOggReader.h
@@ -66,23 +66,16 @@ 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 VideoData* FindStartTime(PRInt64 aOffset,
-                                   PRInt64& aOutStartTime);
-
-  // Get the end time of aEndOffset. This is the playback position we'd reach
-  // after playback finished at aEndOffset.
-  virtual PRInt64 FindEndTime(PRInt64 aEndOffset);
-
   virtual PRBool HasAudio()
   {
     mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mVorbisState != 0 && mVorbisState->mActive;
   }
 
   virtual PRBool HasVideo()
   {
@@ -172,27 +165,35 @@ private:
   // search space. aStartTime must be the presentation time at the start of
   // media, and aEndTime the time at end of media. aRanges must be the time/byte
   // ranges buffered in the media cache as per GetSeekRanges().
   nsresult SeekInUnbuffered(PRInt64 aTarget,
                             PRInt64 aStartTime,
                             PRInt64 aEndTime,
                             const nsTArray<SeekRange>& aRanges);
 
+  // Get the end time of aEndOffset. This is the playback position we'd reach
+  // after playback finished at aEndOffset.
+  PRInt64 RangeEndTime(PRInt64 aEndOffset);
+
   // Get the end time of aEndOffset, without reading before aStartOffset.
   // This is the playback position we'd reach after playback finished at
   // aEndOffset. If PRBool aCachedDataOnly is PR_TRUE, then we'll only read
   // from data which is cached in the media cached, otherwise we'll do
   // regular blocking reads from the media stream. If PRBool aCachedDataOnly
-  // is PR_TRUE, and aState is not mOggState, this can safely be called on
-  // the main thread, otherwise it must be called on the state machine thread.
-  PRInt64 FindEndTime(PRInt64 aStartOffset,
-                      PRInt64 aEndOffset,
-                      PRBool aCachedDataOnly,
-                      ogg_sync_state* aState);
+  // is PR_TRUE, this can safely be called on the main thread, otherwise it
+  // must be called on the state machine thread.
+  PRInt64 RangeEndTime(PRInt64 aStartOffset,
+                       PRInt64 aEndOffset,
+                       PRBool aCachedDataOnly);
+
+  // Get the start time of the range beginning at aOffset. This is the start
+  // time of the first frame and or audio sample we'd be able to play if we
+  // started playback at aOffset.
+  PRInt64 RangeStartTime(PRInt64 aOffset);
 
   // Performs a seek bisection to move the media stream's read cursor to the
   // last ogg page boundary which has end time before aTarget usecs on both the
   // Theora and Vorbis bitstreams. Limits its search to data inside aRange;
   // i.e. it will only read inside of the aRange's start and end offsets.
   // aFuzz is the number of usecs of leniency we'll allow; we'll terminate the
   // seek when we land in the range (aTime - aFuzz, aTime) usecs.
   nsresult SeekBisection(PRInt64 aTarget,
--- a/content/media/raw/nsRawReader.cpp
+++ b/content/media/raw/nsRawReader.cpp
@@ -300,17 +300,12 @@ nsresult nsRawReader::Seek(PRInt64 aTime
     } else {
       video.forget();
     }
   }
 
   return NS_OK;
 }
 
-PRInt64 nsRawReader::FindEndTime(PRInt64 aEndTime)
-{
-  return -1;
-}
-
 nsresult nsRawReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
 {
   return NS_OK;
 }