Bug 563825 - Factor out non-Ogg specific parts of nsOggPlayStateMachine.cpp Part 2 - r=cpearce sr=roc
authorChris Double <chris.double@double.co.nz>
Thu, 06 May 2010 14:31:02 +1200
changeset 41954 7519657a158681491b20ccc0c4e7429a6dd5e175
parent 41953 25f7e6fc9008e45d4341f61207376960010897ce
child 41955 e68cb2f5cd183bf356a00baba94ba33479eae5af
push idunknown
push userunknown
push dateunknown
reviewerscpearce, roc
bugs563825
milestone1.9.3a5pre
Bug 563825 - Factor out non-Ogg specific parts of nsOggPlayStateMachine.cpp Part 2 - r=cpearce sr=roc
content/media/Makefile.in
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/ogg/Makefile.in
content/media/ogg/nsOggDecoder.cpp
content/media/ogg/nsOggReader.cpp
content/media/ogg/nsOggReader.h
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -45,24 +45,28 @@ MODULE = content
 LIBRARY_NAME = gkconmedia_s
 LIBXUL_LIBRARY = 1
 
 EXPORTS = \
   nsMediaDecoder.h \
   nsMediaStream.h \
   nsMediaCache.h \
   nsBuiltinDecoder.h \
+  nsBuiltinDecoderStateMachine.h \
+  nsBuiltinDecoderReader.h \
   VideoUtils.h \
   $(NULL)
 
 CPPSRCS = \
   nsMediaDecoder.cpp \
   nsMediaCache.cpp \
   nsMediaStream.cpp \
   nsBuiltinDecoder.cpp \
+  nsBuiltinDecoderStateMachine.cpp \
+  nsBuiltinDecoderReader.cpp \
   $(NULL)
 
 ifdef MOZ_SYDNEYAUDIO
 EXPORTS += \
   nsAudioStream.h \
   $(NULL)
 CPPSRCS += \
   nsAudioStream.cpp \
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -42,19 +42,17 @@
 #include "nsAudioStream.h"
 #include "nsHTMLVideoElement.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsTArray.h"
 #include "VideoUtils.h"
 #include "nsBuiltinDecoder.h"
 
-using mozilla::Monitor;
-using mozilla::MonitorAutoEnter;
-using mozilla::MonitorAutoExit;
+using namespace mozilla;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gBuiltinDecoderLog;
 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -82,16 +82,83 @@ 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 appropriate to the play state.
 
+An implementation of the nsDecoderStateMachine class is the event
+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 and audio threads are started and video frames displayed at
+  the required time. 
+SEEKING
+  A seek operation is in progress.
+BUFFERING
+  Decoding is paused while data is buffered for smooth playback.
+COMPLETED
+  The resource has completed decoding, but not finished playback. 
+SHUTDOWN
+  The decoder object is about to be destroyed.
+
+The following result in state transitions.
+
+Shutdown()
+  Clean up any resources the nsDecoderStateMachine owns.
+Decode()
+  Start decoding media data.
+Buffer
+  This is not user initiated. It occurs when the
+  available data in the stream drops below a certain point.
+Complete
+  This is not user initiated. It occurs when the
+  stream is completely decoded.
+Seek(float)
+  Seek to the time position given in the resource.
+
+A state transition diagram:
+
+DECODING_METADATA
+  |      |
+  v      | Shutdown()
+  |      |
+  v      -->-------------------->--------------------------|
+  |---------------->----->------------------------|        v
+DECODING             |          |  |              |        |
+  ^                  v Seek(t)  |  |              |        |
+  |         Decode() |          v  |              |        |
+  ^-----------<----SEEKING      |  v Complete     v        v
+  |                  |          |  |              |        |
+  |                  |          |  COMPLETED    SHUTDOWN-<-|
+  ^                  ^          |  |Shutdown()    |
+  |                  |          |  >-------->-----^
+  |         Decode() |Seek(t)   |Buffer()         |
+  -----------<--------<-------BUFFERING           |
+                                |                 ^
+                                v Shutdown()      |
+                                |                 |
+                                ------------>-----|
+
+The following represents the states that the nsBuiltinDecoder object
+can be in, and the valid states the nsDecoderStateMachine can be in at that
+time:
+
+player LOADING   decoder DECODING_METADATA
+player PLAYING   decoder DECODING, BUFFERING, SEEKING, COMPLETED
+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 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
    state machine to start decoding metadata. These are the headers
    that give the video size, framerate, etc.  It returns immediately
@@ -119,17 +186,17 @@ a/v synchronisation is handled by the ns
 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.
 
 This means the owning object of a nsBuiltinDecoder object *MUST* call
-Shutdown when destroying the nsOggDecoder object.  
+Shutdown when destroying the nsBuiltinDecoder object.  
 */
 #if !defined(nsBuiltinDecoder_h_)
 #define nsBuiltinDecoder_h_
 
 #include "nsMediaDecoder.h"
 
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
@@ -153,20 +220,34 @@ static inline PRBool IsCurrentThread(nsI
   return NS_GetCurrentThread() == aThread;
 }
 
 // Decoder backends must implement this class to perform the codec
 // specific parts of decoding the video/audio format.
 class nsDecoderStateMachine : public nsRunnable
 {
 public:
+  // Enumeration for the valid decoding states
+  enum State {
+    DECODER_STATE_DECODING_METADATA,
+    DECODER_STATE_DECODING,
+    DECODER_STATE_SEEKING,
+    DECODER_STATE_BUFFERING,
+    DECODER_STATE_COMPLETED,
+    DECODER_STATE_SHUTDOWN
+  };
+
   // Initializes the state machine, returns NS_OK on success, or
   // NS_ERROR_FAILURE on failure.
   virtual nsresult Init() = 0;
 
+  // Return the current decode state. The decoder monitor must be
+  // obtained before calling this.
+  virtual State GetState() = 0;
+
   // Set the audio volume. The decoder monitor must be obtained before
   // calling this.
   virtual void SetVolume(float aVolume) = 0;
 
   virtual void Shutdown() = 0;
 
   // Called from the main thread to get the duration. The decoder monitor
   // must be obtained before calling this. It is in units of milliseconds.
@@ -199,27 +280,36 @@ public:
   // 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.
   virtual void SetSeekable(PRBool aSeekable) = 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;
 };
 
 class nsBuiltinDecoder : public nsMediaDecoder
 {
   // ISupports
   NS_DECL_ISUPPORTS
 
   // nsIObserver
   NS_DECL_NSIOBSERVER
 
  public:
+  typedef mozilla::Monitor Monitor;
+
   // Enumeration for the valid play states (see mPlayState)
   enum PlayState {
     PLAY_STATE_START,
     PLAY_STATE_LOADING,
     PLAY_STATE_PAUSED,
     PLAY_STATE_PLAYING,
     PLAY_STATE_SEEKING,
     PLAY_STATE_ENDED,
@@ -320,17 +410,17 @@ class nsBuiltinDecoder : public nsMediaD
   }
 
   PRBool OnDecodeThread() {
     return mDecoderStateMachine->OnDecodeThread();
   }
 
   // Returns the monitor for other threads to synchronise access to
   // state.
-  mozilla::Monitor& GetMonitor() { 
+  Monitor& GetMonitor() { 
     return mMonitor; 
   }
 
  public:
   // Return the current state. Can be called on any thread. If called from
   // a non-main thread, the decoder monitor must be held.
   PlayState GetState() {
     return mPlayState;
@@ -347,27 +437,36 @@ class nsBuiltinDecoder : public nsMediaD
 
   // Something has changed that could affect the computed playback rate,
   // so recompute it. The monitor must be held.
   void UpdatePlaybackRate();
 
   // The actual playback rate computation. The monitor must be held.
   double ComputePlaybackRate(PRPackedBool* aReliable);
 
+  // Make the decoder state machine update the playback position. Called by
+  // the reader on the decoder thread (Assertions for this checked by 
+  // mDecoderStateMachine). This must be called with the decode monitor
+  // held.
+  void UpdatePlaybackPosition(PRInt64 aTime)
+  {
+    mDecoderStateMachine->UpdatePlaybackPosition(aTime);
+  }
+
   /****** 
    * The following methods must only be called on the main
    * thread.
    ******/
 
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
   void ChangeState(PlayState aState);
 
-  // Called when the metadata from the Ogg file has been read.
+  // Called when the metadata from the media file has been read.
   // Call on the main thread only.
   void MetadataLoaded();
 
   // Called when the first frame has been loaded.
   // Call on the main thread only.
   void FirstFrameLoaded();
 
   // Called when the video has completed playing.
@@ -403,17 +502,24 @@ class nsBuiltinDecoder : public nsMediaD
 
   // Find the end of the cached data starting at the current decoder
   // position.
   PRInt64 GetDownloadPosition();
 
   // Updates the approximate byte offset which playback has reached. This is
   // used to calculate the readyState transitions.
   void UpdatePlaybackOffset(PRInt64 aOffset);
-  
+
+  // Provide access to the state machine object
+  nsDecoderStateMachine* GetStateMachine() { return mDecoderStateMachine; }
+
+  // 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();
 
   /******
    * The following members should be accessed with the decoder lock held.
    ******/
 
@@ -448,17 +554,17 @@ public:
   // Position to seek to when the seek notification is received by the
   // decode thread. Written by the main thread and read via the
   // decode thread. Synchronised using mMonitor. If the
   // value is negative then no seek has been requested. When a seek is
   // started this is reset to negative.
   float mRequestedSeekTime;
 
   // Duration of the media resource. Set to -1 if unknown.
-  // Set when the Ogg metadata is loaded. Accessed on the main thread
+  // Set when the metadata is loaded. Accessed on the main thread
   // only.
   PRInt64 mDuration;
 
   // True if the media resource is seekable (server supports byte range
   // requests).
   PRPackedBool mSeekable;
 
   /******
@@ -473,17 +579,17 @@ public:
   nsCOMPtr<nsDecoderStateMachine> mDecoderStateMachine;
 
   // Stream of media data.
   nsAutoPtr<nsMediaStream> mStream;
 
   // Monitor for detecting when the video play state changes. A call
   // to Wait on this monitor will block the thread until the next
   // state change.
-  mozilla::Monitor mMonitor;
+  Monitor mMonitor;
 
   // Set to one of the valid play states. It is protected by the
   // monitor mMonitor. This monitor must be acquired when reading or
   // writing the state. Any change to the state on the main thread
   // must call NotifyAll on the monitor so the decode thread can wake up.
   PlayState mPlayState;
 
   // The state to change to after a seek or load operation. It must only
--- a/content/media/nsBuiltinDecoderReader.cpp
+++ b/content/media/nsBuiltinDecoderReader.cpp
@@ -35,24 +35,23 @@
  * 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 ***** */
 
 #include "nsISeekableStream.h"
 #include "nsClassHashtable.h"
 #include "nsTArray.h"
-#include "nsOggDecoder.h"
-#include "nsOggReader.h"
-#include "nsOggCodecState.h"
-#include "nsOggPlayStateMachine.h"
+#include "nsBuiltinDecoder.h"
+#include "nsBuiltinDecoderReader.h"
+#include "nsBuiltinDecoderStateMachine.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 
-using mozilla::MonitorAutoExit;
+using namespace mozilla;
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gBuiltinDecoderLog;
 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
 #ifdef SEEK_LOGGING
@@ -60,524 +59,91 @@ extern PRLogModuleInfo* gBuiltinDecoderL
 #else
 #define SEEK_LOG(type, msg)
 #endif
 #else
 #define LOG(type, msg)
 #define SEEK_LOG(type, msg)
 #endif
 
-// Chunk size to read when reading Ogg files. Average Ogg page length
-// is about 4300 bytes, so we read the file in chunks larger than that.
-static const int PAGE_STEP = 8192;
-
 // 32 bit integer multiplication with overflow checking. Returns PR_TRUE
 // if the multiplication was successful, or PR_FALSE if the operation resulted
 // in an integer overflow.
 PRBool MulOverflow32(PRUint32 a, PRUint32 b, PRUint32& aResult) {
   PRUint64 a64 = a;
   PRUint64 b64 = b;
   PRUint64 r64 = a64 * b64;
   if (r64 > PR_UINT32_MAX)
     return PR_FALSE;
   aResult = static_cast<PRUint32>(r64);
   return PR_TRUE;
 }
 
 VideoData* VideoData::Create(PRInt64 aOffset,
                              PRInt64 aTime,
-                             th_ycbcr_buffer aBuffer,
+                             const YCbCrBuffer &aBuffer,
                              PRBool aKeyframe,
-                             PRInt64 aGranulepos)
+                             PRInt64 aTimecode)
 {
-  nsAutoPtr<VideoData> v(new VideoData(aOffset, aTime, aKeyframe, aGranulepos));
+  nsAutoPtr<VideoData> v(new VideoData(aOffset, aTime, aKeyframe, aTimecode));
   for (PRUint32 i=0; i < 3; ++i) {
     PRUint32 size = 0;
-    if (!MulOverflow32(PR_ABS(aBuffer[i].height),
-                       PR_ABS(aBuffer[i].stride),
+    if (!MulOverflow32(PR_ABS(aBuffer.mPlanes[i].mHeight),
+                       PR_ABS(aBuffer.mPlanes[i].mStride),
                        size))
     {
       // Invalid frame size. Skip this plane. The plane will have 0
       // dimensions, thanks to our constructor.
       continue;
     }
     unsigned char* p = static_cast<unsigned char*>(moz_xmalloc(size));
     if (!p) {
       NS_WARNING("Failed to allocate memory for video frame");
       return nsnull;
     }
-    v->mBuffer[i].data = p;
-    v->mBuffer[i].width = aBuffer[i].width;
-    v->mBuffer[i].height = aBuffer[i].height;
-    v->mBuffer[i].stride = aBuffer[i].stride;
-    memcpy(v->mBuffer[i].data, aBuffer[i].data, size);
+    v->mBuffer.mPlanes[i].mData = p;
+    v->mBuffer.mPlanes[i].mWidth = aBuffer.mPlanes[i].mWidth;
+    v->mBuffer.mPlanes[i].mHeight = aBuffer.mPlanes[i].mHeight;
+    v->mBuffer.mPlanes[i].mStride = aBuffer.mPlanes[i].mStride;
+    memcpy(v->mBuffer.mPlanes[i].mData, aBuffer.mPlanes[i].mData, size);
   }
   return v.forget();
 }
 
-nsOggReader::nsOggReader(nsOggPlayStateMachine* aStateMachine)
-  : mMonitor("media.oggreader"),
-    mPlayer(aStateMachine),
-    mTheoraState(nsnull),
-    mVorbisState(nsnull),
-    mPageOffset(0),
-    mDataOffset(0),
-    mTheoraGranulepos(-1),
-    mVorbisGranulepos(-1),
-    mCallbackPeriod(0)
+nsBuiltinDecoderReader::nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder)
+  : mMonitor("media.decoderreader"),
+    mDecoder(aDecoder),
+    mDataOffset(0)
 {
-  MOZ_COUNT_CTOR(nsOggReader);
+  MOZ_COUNT_CTOR(nsBuiltinDecoderReader);
 }
 
-nsOggReader::~nsOggReader()
+nsBuiltinDecoderReader::~nsBuiltinDecoderReader()
 {
   ResetDecode();
-  ogg_sync_clear(&mOggState);
-  MOZ_COUNT_DTOR(nsOggReader);
+  MOZ_COUNT_DTOR(nsBuiltinDecoderReader);
 }
 
-nsresult nsOggReader::Init() {
-  PRBool init = mCodecStates.Init();
-  NS_ASSERTION(init, "Failed to initialize mCodecStates");
-  if (!init) {
-    return NS_ERROR_FAILURE;
-  }
-  int ret = ogg_sync_init(&mOggState);
-  NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
-  return NS_OK;
-}
-
-nsresult nsOggReader::ResetDecode()
+nsresult nsBuiltinDecoderReader::ResetDecode()
 {
   nsresult res = NS_OK;
 
-  // Clear the Theora/Vorbis granulepos capture status, so that the next
-  // decode calls recaptures the granulepos.
-  mTheoraGranulepos = -1;
-  mVorbisGranulepos = -1;
-
   mVideoQueue.Reset();
   mAudioQueue.Reset();
 
-  MonitorAutoEnter mon(mMonitor);
-
-  // 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;
 }
 
-nsresult nsOggReader::DecodeVorbis(nsTArray<SoundData*>& aChunks,
-                                   ogg_packet* aPacket)
-{
-  // Successfully read a packet.
-  if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
-    return NS_ERROR_FAILURE;
-  }
-  if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
-                               &mVorbisState->mBlock) != 0)
-  {
-    return NS_ERROR_FAILURE;
-  }
-
-  float** pcm = 0;
-  PRUint32 samples = 0;
-  PRUint32 channels = mVorbisState->mInfo.channels;
-  while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
-    if (samples > 0) {
-      float* buffer = new float[samples * channels];
-      float* p = buffer;
-      for (PRUint32 i = 0; i < samples; ++i) {
-        for (PRUint32 j = 0; j < channels; ++j) {
-          *p++ = pcm[j][i];
-        }
-      }
-
-      PRInt64 duration = mVorbisState->Time((PRInt64)samples);
-      PRInt64 startTime = (mVorbisGranulepos != -1) ?
-        mVorbisState->Time(mVorbisGranulepos) : -1;
-      SoundData* s = new SoundData(mPageOffset,
-                                   startTime,
-                                   duration,
-                                   samples,
-                                   buffer,
-                                   channels);
-      if (mVorbisGranulepos != -1) {
-        mVorbisGranulepos += samples;
-      }
-      aChunks.AppendElement(s);
-    }
-    if (vorbis_synthesis_read(&mVorbisState->mDsp, samples) != 0) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-  return NS_OK;
-}
-
-// Decode page, calculate timestamps.
-PRBool nsOggReader::DecodeAudioPage()
-{
-  MonitorAutoEnter mon(mMonitor);
-  NS_ASSERTION(mPlayer->OnStateMachineThread() || mPlayer->OnDecodeThread(),
-               "Should be on playback or decode thread.");
-  NS_ASSERTION(mVorbisState!=0, "Need Vorbis state to decode audio");
-  ogg_packet packet;
-  packet.granulepos = -1;
-
-  PRBool endOfStream = PR_FALSE;
-
-  nsAutoTArray<SoundData*, 64> chunks;
-  if (mVorbisGranulepos == -1) {
-    // Not captured Vorbis granulepos, read up until we get a granulepos, and
-    // back propagate the granulepos.
-
-    // We buffer the packets' pcm samples until we reach a packet with a granulepos.
-    // This will be the last packet in a page. Then using that granulepos to 
-    // calculate the packet's end time, we calculate all the packets' start times by
-    // subtracting their durations.
-
-    // Ensure we've got Vorbis packets; read one more Vorbis page if necessary.
-    while (packet.granulepos <= 0 && !endOfStream) {
-      if (!ReadOggPacket(mVorbisState, &packet)) {
-        endOfStream = PR_TRUE;
-        break;
-      }
-      if (packet.e_o_s != 0) {
-        // This packet marks the logical end of the Vorbis bitstream. It may
-        // still contain sound samples, so we must still decode it.
-        endOfStream = PR_TRUE;
-      }
-
-      if (NS_FAILED(DecodeVorbis(chunks, &packet))) {
-        NS_WARNING("Failed to decode Vorbis packet");
-      }
-    }
-
-    if (packet.granulepos > 0) {
-      // Successfully read up to a non -1 granulepos.
-      // Calculate the timestamps of the sound samples.
-      PRInt64 granulepos = packet.granulepos; // Represents end time of last sample.
-      mVorbisGranulepos = packet.granulepos;
-      for (int i = chunks.Length() - 1; i >= 0; --i) {
-        SoundData* s = chunks[i];
-        PRInt64 startGranule = granulepos - s->mSamples;
-        s->mTime = mVorbisState->Time(startGranule);
-        granulepos = startGranule;
-      }
-    }
-  } else {
-    // We have already captured the granulepos. The next packet's granulepos
-    // is its number of samples, plus the previous granulepos.
-    if (!ReadOggPacket(mVorbisState, &packet)) {
-      endOfStream = PR_TRUE;
-    } else {
-      // Successfully read a packet from the file. Decode it.
-      endOfStream = packet.e_o_s != 0;
-
-      // Try to decode any packet we've read.
-      if (NS_FAILED(DecodeVorbis(chunks, &packet))) {
-        NS_WARNING("Failed to decode Vorbis packet");
-      }
-
-      if (packet.granulepos != -1 && packet.granulepos != mVorbisGranulepos) {
-        // If the packet's granulepos doesn't match our running sample total,
-        // it's likely the bitstream has been damaged somehow, or perhaps
-        // oggz-chopped. Just assume the packet's granulepos is correct...
-        mVorbisGranulepos = packet.granulepos;
-      }
-    }
-  }
-
-  // We've successfully decoded some sound chunks. Push them onto the audio
-  // queue.
-  for (PRUint32 i = 0; i < chunks.Length(); ++i) {
-    mAudioQueue.Push(chunks[i]);
-  }
-
-  if (endOfStream) {
-    // We've encountered an end of bitstream packet, or we've hit the end of
-    // file while trying to decode, so inform the audio queue that there'll
-    // be no more samples.
-    mAudioQueue.Finish();
-    return PR_FALSE;
-  }
-
-  return PR_TRUE;
-}
-
-// Returns 1 if the Theora info struct is decoding a media of Theora
-// verion (maj,min,sub) or later, otherwise returns 0.
-static int
-TheoraVersion(th_info* info,
-              unsigned char maj,
-              unsigned char min,
-              unsigned char sub)
-{
-  ogg_uint32_t ver = (maj << 16) + (min << 8) + sub;
-  ogg_uint32_t th_ver = (info->version_major << 16) +
-                        (info->version_minor << 8) +
-                        info->version_subminor;
-  return (th_ver >= ver) ? 1 : 0;
-}
-
-#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<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->mGranulepos;
-  }
-  return PR_TRUE;
-}
-#endif
-
-static void Clear(nsTArray<VideoData*>& aFrames) {
-  for (PRUint32 i = 0; i < aFrames.Length(); ++i) {
-    delete aFrames[i];
-  }
-  aFrames.Clear();
-}
-
-nsresult nsOggReader::DecodeTheora(nsTArray<VideoData*>& aFrames,
-                                   ogg_packet* aPacket)
+nsresult nsBuiltinDecoderReader::GetBufferedBytes(nsTArray<ByteRange>& aRanges)
 {
-  int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
-  if (ret != 0 && ret != TH_DUPFRAME) {
-    return NS_ERROR_FAILURE;
-  }
-  PRInt64 time = (aPacket->granulepos != -1)
-    ? mTheoraState->StartTime(aPacket->granulepos) : -1;
-  if (ret == TH_DUPFRAME) {
-    aFrames.AppendElement(VideoData::CreateDuplicate(mPageOffset,
-                                                     time,
-                                                     aPacket->granulepos));
-  } else if (ret == 0) {
-    th_ycbcr_buffer buffer;
-    ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
-    NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
-    PRBool isKeyframe = th_packet_iskeyframe(aPacket) == 1;
-    VideoData *v = VideoData::Create(mPageOffset,
-                                     time,
-                                     buffer,
-                                     isKeyframe,
-                                     aPacket->granulepos);
-    if (!v) {
-      NS_WARNING("Failed to allocate memory for video frame");
-      Clear(aFrames);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    aFrames.AppendElement(v);
-  }
-  return NS_OK;
-}
-
-PRBool nsOggReader::DecodeVideoPage(PRBool &aKeyframeSkip,
-                                    PRInt64 aTimeThreshold)
-{
-  MonitorAutoEnter mon(mMonitor);
-  NS_ASSERTION(mPlayer->OnStateMachineThread() || mPlayer->OnDecodeThread(),
-               "Should be on state machine or AV thread.");
-  // We chose to keep track of the Theora granulepos ourselves, rather than
-  // rely on th_decode_packetin() to do it for us. This is because
-  // th_decode_packetin() simply works by incrementing a counter every time
-  // it's called, so if we drop frames and don't call it, subsequent granulepos
-  // will be wrong. Whenever we read a packet which has a granulepos, we use
-  // its granulepos, otherwise we increment the previous packet's granulepos.
-
-  nsAutoTArray<VideoData*, 8> frames;
-  ogg_packet packet;
-  PRBool endOfStream = PR_FALSE;
-  if (mTheoraGranulepos == -1) {
-    // We've not read a Theora packet with a granulepos, so we don't know what
-    // timestamp to assign to Theora frames we decode. This will only happen
-    // the first time we read, or after a seek. We must read and buffer up to
-    // the first Theora packet with a granulepos, and back-propagate its 
-    // granulepos to calculate the buffered frames' granulepos.
-    do {
-      if (!ReadOggPacket(mTheoraState, &packet)) {
-        // Failed to read another page, must be the end of file. We can't have
-        // already encountered an end of bitstream packet, else we wouldn't be
-        // here, so this bitstream must be missing its end of stream packet, or
-        // is otherwise corrupt (oggz-chop can output files like this). Inform
-        // the queue that there will be no more frames.
-        mVideoQueue.Finish();
-        return PR_FALSE;
-      }
-
-      if (packet.granulepos > 0) {
-        // We've found a packet with a granulepos, we can now determine the
-        // buffered packet's timestamps, as well as the timestamps for any
-        // packets we read subsequently.
-        mTheoraGranulepos = packet.granulepos;
-      }
-    
-      if (DecodeTheora(frames, &packet) == NS_ERROR_OUT_OF_MEMORY) {
-        NS_WARNING("Theora decode memory allocation failure!");
-        return PR_FALSE;
-      }
-
-    } while (packet.granulepos <= 0 && !endOfStream);
-
-    if (packet.granulepos > 0) {
-      // We have captured a granulepos. Backpropagate the granulepos
-      // to determine buffered packets' timestamps.
-      PRInt64 succGranulepos = packet.granulepos;
-      int version_3_2_1 = TheoraVersion(&mTheoraState->mInfo,3,2,1);
-      int shift = mTheoraState->mInfo.keyframe_granule_shift;
-      for (int i = frames.Length() - 2; i >= 0; --i) {
-        PRInt64 granulepos = succGranulepos;
-        if (frames[i]->mKeyframe) {
-          // This frame is a keyframe. It's granulepos is the previous granule
-          // number minus 1, shifted by granuleshift.
-          ogg_int64_t frame_index = th_granule_frame(mTheoraState->mCtx,
-                                                     granulepos);
-          granulepos = (frame_index + version_3_2_1 - 1) << shift;
-          // Theora 3.2.1+ granulepos store frame number [1..N], so granulepos
-          // should be > 0.
-          // Theora 3.2.0 granulepos store the frame index [0..(N-1)], so
-          // granulepos should be >= 0. 
-          NS_ASSERTION((version_3_2_1 && granulepos > 0) ||
-                       granulepos >= 0, "Should have positive granulepos");
-        } else {
-          // Packet is not a keyframe. It's granulepos depends on its successor
-          // packet...
-          if (frames[i+1]->mKeyframe) {
-            // The successor frame is a keyframe, so we can't just subtract 1
-            // from the "keyframe offset" part of its granulepos, as it
-            // doesn't have one! So fake it, take the keyframe offset as the
-            // max possible keyframe offset. This means the granulepos (probably)
-            // overshoots and claims that it depends on a frame before its actual
-            // keyframe but at least its granule number will be correct, so the
-            // times we calculate from this granulepos will also be correct.
-            ogg_int64_t frameno = th_granule_frame(mTheoraState->mCtx,
-                                                   granulepos);
-            ogg_int64_t max_offset = NS_MIN((frameno - 1),
-                                         (ogg_int64_t)(1 << shift) - 1);
-            ogg_int64_t granule = frameno +
-                                  TheoraVersion(&mTheoraState->mInfo,3,2,1) -
-                                  1 - max_offset;
-            NS_ASSERTION(granule > 0, "Must have positive granulepos");
-            granulepos = (granule << shift) + max_offset;
-          } else {
-            // Neither previous nor this frame are keyframes, so we can just
-            // decrement the previous granulepos to calculate this frames
-            // granulepos.
-            --granulepos;
-          }
-        }
-        // Check that the frame's granule number (it's frame number) is
-        // one less than the successor frame.
-        NS_ASSERTION(th_granule_frame(mTheoraState->mCtx, succGranulepos) ==
-                     th_granule_frame(mTheoraState->mCtx, granulepos) + 1,
-                     "Granulepos calculation is incorrect!");
-        frames[i]->mTime = mTheoraState->StartTime(granulepos);
-        frames[i]->mGranulepos = granulepos;
-        succGranulepos = granulepos;
-        NS_ASSERTION(frames[i]->mTime < frames[i+1]->mTime, "Times should increase");      
-      }
-      NS_ASSERTION(AllFrameTimesIncrease(frames), "All frames must have granulepos");
-    }
-  } else {
-    
-    NS_ASSERTION(mTheoraGranulepos > 0, "We must Theora granulepos!");
-    
-    if (!ReadOggPacket(mTheoraState, &packet)) {
-      // Failed to read from file, so EOF or other premature failure.
-      // Inform the queue that there will be no more frames.
-      mVideoQueue.Finish();
-      return PR_FALSE;
-    }
-
-    endOfStream = packet.e_o_s != 0;
-
-    // Maintain the Theora granulepos. We must do this even if we drop frames,
-    // otherwise our clock will be wrong after we've skipped frames.
-    if (packet.granulepos != -1) {
-      // Incoming packet has a granulepos, use that as it's granulepos.
-      mTheoraGranulepos = packet.granulepos;
-    } else {
-      // Increment the previous Theora granulepos.
-      PRInt64 granulepos = 0;
-      int shift = mTheoraState->mInfo.keyframe_granule_shift;
-      // Theora 3.2.1+ bitstreams granulepos store frame number; [1..N]
-      // Theora 3.2.0 bitstreams store the frame index; [0..(N-1)]
-      if (!th_packet_iskeyframe(&packet)) {
-        granulepos = mTheoraGranulepos + 1;
-      } else {
-        ogg_int64_t frameindex = th_granule_frame(mTheoraState->mCtx,
-                                                  mTheoraGranulepos);
-        ogg_int64_t granule = frameindex +
-                              TheoraVersion(&mTheoraState->mInfo,3,2,1) + 1;
-        NS_ASSERTION(granule > 0, "Must have positive granulepos");
-        granulepos = granule << shift;
-      }
-
-      NS_ASSERTION(th_granule_frame(mTheoraState->mCtx, mTheoraGranulepos) + 1 == 
-                   th_granule_frame(mTheoraState->mCtx, granulepos),
-                   "Frame number must increment by 1");
-      packet.granulepos = mTheoraGranulepos = granulepos;
-    }
-
-    PRInt64 time = mTheoraState->StartTime(mTheoraGranulepos);
-    NS_ASSERTION(packet.granulepos != -1, "Must know packet granulepos");
-    if (!aKeyframeSkip ||
-        (th_packet_iskeyframe(&packet) == 1 && time >= aTimeThreshold))
-    {
-      if (DecodeTheora(frames, &packet) == NS_ERROR_OUT_OF_MEMORY) {
-        NS_WARNING("Theora decode memory allocation failure");
-        return PR_FALSE;
-      }
-    }
-  }
-
-  // Push decoded data into the video frame queue.
-  for (PRUint32 i = 0; i < frames.Length(); i++) {
-    nsAutoPtr<VideoData> data(frames[i]);
-    if (!aKeyframeSkip || (aKeyframeSkip && frames[i]->mKeyframe)) {
-      mVideoQueue.Push(data.forget());
-      if (aKeyframeSkip && frames[i]->mKeyframe) {
-        aKeyframeSkip = PR_FALSE;
-      }
-    } else {
-      frames[i] = nsnull;
-      data = nsnull;
-    }
-  }
-
-  if (endOfStream) {
-    // We've encountered an end of bitstream packet. Inform the queue that
-    // there will be no more frames.
-    mVideoQueue.Finish();
-  }
-
-  return !endOfStream;
-}
-
-nsresult nsOggReader::GetBufferedBytes(nsTArray<ByteRange>& aRanges)
-{
-  NS_ASSERTION(mPlayer->OnStateMachineThread(),
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
   mMonitor.AssertCurrentThreadIn();
   PRInt64 startOffset = mDataOffset;
-  nsMediaStream* stream = mPlayer->mDecoder->GetCurrentStream();
+  nsMediaStream* stream = mDecoder->GetCurrentStream();
   while (PR_TRUE) {
     PRInt64 endOffset = stream->GetCachedDataEnd(startOffset);
     if (endOffset == startOffset) {
       // Uncached at startOffset.
       endOffset = stream->GetNextCachedData(startOffset);
       if (endOffset == -1) {
         // Uncached at startOffset until endOffset of stream, or we're at
         // the end of stream.
@@ -604,26 +170,26 @@ nsresult nsOggReader::GetBufferedBytes(n
   }
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 ByteRange
-nsOggReader::GetSeekRange(const nsTArray<ByteRange>& ranges,
-                          PRInt64 aTarget,
-                          PRInt64 aStartTime,
-                          PRInt64 aEndTime,
-                          PRBool aExact)
+nsBuiltinDecoderReader::GetSeekRange(const nsTArray<ByteRange>& ranges,
+                                     PRInt64 aTarget,
+                                     PRInt64 aStartTime,
+                                     PRInt64 aEndTime,
+                                     PRBool aExact)
 {
-  NS_ASSERTION(mPlayer->OnStateMachineThread(),
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
   PRInt64 so = mDataOffset;
-  PRInt64 eo = mPlayer->mDecoder->GetCurrentStream()->GetLength();
+  PRInt64 eo = mDecoder->GetCurrentStream()->GetLength();
   PRInt64 st = aStartTime;
   PRInt64 et = aEndTime;
   for (PRUint32 i = 0; i < ranges.Length(); i++) {
     const ByteRange &r = ranges[i];
     if (r.mTimeStart < aTarget) {
       so = r.mOffsetStart;
       st = r.mTimeStart;
     }
@@ -635,912 +201,68 @@ nsOggReader::GetSeekRange(const nsTArray
     if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
       // Target lies exactly in this range.
       return ranges[i];
     }
   }
   return aExact ? ByteRange() : ByteRange(so, eo, st, et);
 }
 
-nsresult nsOggReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime)
-{
-  MonitorAutoEnter mon(mMonitor);
-  NS_ASSERTION(mPlayer->OnStateMachineThread(),
-               "Should be on state machine thread.");
-  LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mPlayer->mDecoder, aTarget));
-  nsMediaStream* stream = mPlayer->mDecoder->GetCurrentStream();
-
-  if (NS_FAILED(ResetDecode())) {
-    return NS_ERROR_FAILURE;
-  }
-  if (aTarget == aStartTime) {
-    stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
-    mPageOffset = mDataOffset;
-    NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
-    {
-      MonitorAutoExit exitReaderMon(mMonitor);
-      MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-      mPlayer->UpdatePlaybackPosition(aStartTime);
-    }
-  } else {
-
-    // Determine the already downloaded data in the media cache. 
-    nsAutoTArray<ByteRange, 16> ranges;
-    stream->Pin();
-    if (NS_FAILED(GetBufferedBytes(ranges))) {
-      stream->Unpin();
-      return NS_ERROR_FAILURE;
-    }
-
-    // Try to seek in the cached data ranges first, before falling back to
-    // seeking over the network. This makes seeking in buffered ranges almost
-    // instantaneous.
-    ByteRange r = GetSeekRange(ranges, aTarget, aStartTime, aEndTime, PR_TRUE);
-    nsresult res = NS_ERROR_FAILURE;
-    if (!r.IsNull()) {
-      // The frame should be in this buffered range. Seek exactly there.
-      res = SeekBisection(aTarget, r, 0);
-
-      if (NS_SUCCEEDED(res) && HasVideo()) {
-        // 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 = !DecodeVideoPage(skip, 0);
-          {
-            MonitorAutoExit exitReaderMon(mMonitor);
-            MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-            if (mPlayer->mState == nsOggPlayStateMachine::DECODER_STATE_SHUTDOWN) {
-              stream->Unpin();
-              return NS_ERROR_FAILURE;
-            }
-          }
-        } while (!eof &&
-                 mVideoQueue.GetSize() == 0);
-      
-        VideoData* video = mVideoQueue.PeekFront();
-        if (video && !video->mKeyframe) {
-          // First decoded frame isn't a keyframe, seek back to previous keyframe,
-          // otherwise we'll get visual artifacts.
-          NS_ASSERTION(video->mGranulepos != -1, "Must have a granulepos");
-          int shift = mTheoraState->mInfo.keyframe_granule_shift;
-          PRInt64 keyframeGranulepos = (video->mGranulepos >> shift) << shift;
-          PRInt64 keyframeTime = mTheoraState->StartTime(keyframeGranulepos);
-          
-          SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it",
-                                  video->mTime, keyframeTime));
-          ByteRange k = GetSeekRange(ranges,
-                                     keyframeTime,
-                                     aStartTime,
-                                     aEndTime,
-                                     PR_FALSE);
-          res = SeekBisection(keyframeTime, k, 500);
-          NS_ASSERTION(mTheoraGranulepos == -1, "SeekBisection must reset Theora decode");
-          NS_ASSERTION(mVorbisGranulepos == -1, "SeekBisection must reset Vorbis decode");
-        }
-      }
-    }
-
-    stream->Unpin();
-
-    if (NS_FAILED(res)) {
-      // We failed to find the seek target (or perhaps its keyframe, somehow?)
-      // in a buffered range. Minimize the bisection search space using the
-      // buffered ranges, and perform a bisection search.
-
-      // If we've got an active Theora bitstream, determine the maximum possible
-      // time in ms which a keyframe could be before a given interframe. We
-      // subtract this from our seek target, seek to the new target, and then
-      // decode forwards to the original seek target. We should encounter a
-      // keyframe in that interval. This prevents us from needing to run two
-      // bisections; one for the seek target frame, and another to find its
-      // keyframe. It's usually faster to just download this extra data, rather
-      // tham perform two bisections to find the seek target's keyframe. We
-      // don't do this offsetting when seeking in a buffered ranges (above),
-      // as the extra decoding causes a noticable speed hit when all the data
-      // is buffered.
-      PRInt64 keyframeOffsetMs = 0;
-      if (HasVideo() && mTheoraState) {
-        keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
-      }
-      PRInt64 seekTarget = NS_MAX(aStartTime, aTarget - keyframeOffsetMs);
-
-      ByteRange k = GetSeekRange(ranges, seekTarget, aStartTime, aEndTime, PR_FALSE);
-      res = SeekBisection(seekTarget, k, 500);
-
-      NS_ENSURE_SUCCESS(res, res);
-      NS_ASSERTION(mTheoraGranulepos == -1, "SeekBisection must reset Theora decode");
-      NS_ASSERTION(mVorbisGranulepos == -1, "SeekBisection must reset Vorbis decode");
-    }
-  }
-
-  // Decode forward to the seek target frame. Start with video, if we have it.
-  // We should pass a keyframe while doing this.
-  if (HasVideo()) {
-    nsAutoPtr<VideoData> video;
-    PRBool eof = PR_FALSE;
-    PRInt64 startTime = -1;
-    video = nsnull;
-    while (HasVideo() && !eof) {
-      while (mVideoQueue.GetSize() == 0 && !eof) {
-        PRBool skip = PR_FALSE;
-        eof = !DecodeVideoPage(skip, 0);
-        {
-          MonitorAutoExit exitReaderMon(mMonitor);
-          MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-          if (mPlayer->mState == nsOggPlayStateMachine::DECODER_STATE_SHUTDOWN) {
-            return NS_ERROR_FAILURE;
-          }
-        }
-      }
-      if (mVideoQueue.GetSize() == 0) {
-        break;
-      }
-      video = mVideoQueue.PeekFront();
-      // If the frame end time is less than the seek target, we won't want
-      // to display this frame after the seek, so discard it.
-      if (video && video->mTime + mCallbackPeriod < aTarget) {
-        if (startTime == -1) {
-          startTime = video->mTime;
-        }
-        mVideoQueue.PopFront();
-        video = nsnull;
-      } else {
-        video.forget();
-        break;
-      }
-    }
-    {
-      MonitorAutoExit exitReaderMon(mMonitor);
-      MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-      if (mPlayer->mState == nsOggPlayStateMachine::DECODER_STATE_SHUTDOWN) {
-        return NS_ERROR_FAILURE;
-      }
-    }
-    SEEK_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime));
-  }
-
-  if (HasAudio()) {
-    // Decode audio forward to the seek target.
-    nsAutoPtr<SoundData> audio;
-    bool eof = PR_FALSE;
-    while (HasAudio() && !eof) {
-      while (!eof && mAudioQueue.GetSize() == 0) {
-        eof = !DecodeAudioPage();
-        {
-          MonitorAutoExit exitReaderMon(mMonitor);
-          MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-          if (mPlayer->mState == nsOggPlayStateMachine::DECODER_STATE_SHUTDOWN) {
-            return NS_ERROR_FAILURE;
-          }
-        }
-      }
-      audio = mAudioQueue.PeekFront();
-      if (audio && audio->mTime + audio->mDuration <= aTarget) {
-        mAudioQueue.PopFront();
-        audio = nsnull;
-      } else {
-        audio.forget();
-        break;
-      }
-    }
-  }
-
-  return NS_OK;
-}
-
-enum PageSyncResult {
-  PAGE_SYNC_ERROR = 1,
-  PAGE_SYNC_END_OF_RANGE= 2,
-  PAGE_SYNC_OK = 3
-};
-
-// Reads a page from the media stream.
-static PageSyncResult
-PageSync(ogg_sync_state* aState,
-         nsMediaStream* aStream,
-         PRInt64 aEndOffset,
-         ogg_page* aPage,
-         int& aSkippedBytes)
-{
-  aSkippedBytes = 0;
-  // Sync to the next page.
-  int ret = 0;
-  PRUint32 bytesRead = 0;
-  while (ret <= 0) {
-    ret = ogg_sync_pageseek(aState, aPage);
-    if (ret == 0) {
-      char* buffer = ogg_sync_buffer(aState, PAGE_STEP);
-      NS_ASSERTION(buffer, "Must have a buffer");
-
-      // Read from the file into the buffer
-      PRInt64 bytesToRead = NS_MIN(static_cast<PRInt64>(PAGE_STEP),
-                                   aEndOffset - aStream->Tell());
-      if (bytesToRead <= 0) {
-        return PAGE_SYNC_END_OF_RANGE;
-      }
-      nsresult rv = aStream->Read(buffer,
-                                  static_cast<PRUint32>(bytesToRead),
-                                  &bytesRead);
-      if (NS_FAILED(rv)) {
-        return PAGE_SYNC_ERROR;
-      }
-
-      if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
-        // End of file.
-        return PAGE_SYNC_END_OF_RANGE;
-      }
-
-      // Update the synchronisation layer with the number
-      // of bytes written to the buffer
-      ret = ogg_sync_wrote(aState, bytesRead);
-      NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);    
-      continue;
-    }
-
-    if (ret < 0) {
-      NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
-      aSkippedBytes += -ret;
-      NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
-      continue;
-    }
-  }
-  
-  return PAGE_SYNC_OK;
-}
-
-nsresult nsOggReader::SeekBisection(PRInt64 aTarget,
-                                    const ByteRange& aRange,
-                                    PRUint32 aFuzz)
-{
-  NS_ASSERTION(mPlayer->OnStateMachineThread(),
-               "Should be on state machine thread.");
-  nsMediaStream* stream = mPlayer->mDecoder->GetCurrentStream();
-
-  if (aTarget == aRange.mTimeStart) {
-    if (NS_FAILED(ResetDecode())) {
-      return NS_ERROR_FAILURE;
-    }
-    stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
-    mPageOffset = mDataOffset;
-    return NS_OK;
-  }
-
-  // Bisection search, find start offset of last page with end time less than
-  // the seek target.
-  ogg_int64_t startOffset = aRange.mOffsetStart;
-  ogg_int64_t startTime = aRange.mTimeStart;
-  ogg_int64_t startLength = 0;
-  ogg_int64_t endOffset = aRange.mOffsetEnd;
-  ogg_int64_t endTime = aRange.mTimeEnd;
-
-  ogg_int64_t seekTarget = aTarget;
-  PRInt64 seekLowerBound = NS_MAX(static_cast<PRInt64>(0), aTarget - aFuzz);
-  int hops = 0;
-  ogg_int64_t previousGuess = -1;
-  int backsteps = 1;
-  const int maxBackStep = 10;
-  NS_ASSERTION(static_cast<PRUint64>(PAGE_STEP) * pow(2.0, maxBackStep) < PR_INT32_MAX,
-               "Backstep calculation must not overflow");
-  while (PR_TRUE) {
-    ogg_int64_t duration = 0;
-    double target = 0;
-    ogg_int64_t interval = 0;
-    ogg_int64_t guess = 0;
-    ogg_page page;
-    int skippedBytes = 0;
-    ogg_int64_t pageOffset = 0;
-    ogg_int64_t pageLength = 0;
-    int backoff = 0;
-    ogg_int64_t granuleTime = -1;
-    PRInt64 oldPageOffset = 0;
-
-    // Guess where we should bisect to, based on the bit rate and the time
-    // remaining in the interval.
-    while (PR_TRUE) {
-  
-      // Discard any previously buffered packets/pages.
-      if (NS_FAILED(ResetDecode())) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // Guess bisection point.
-      duration = endTime - startTime;
-      target = (double)(seekTarget - startTime) / (double)duration;
-      interval = endOffset - startOffset - startLength;
-      guess = startOffset + startLength +
-              (ogg_int64_t)((double)interval * target) - backoff;
-      guess = NS_MIN(guess, endOffset - PAGE_STEP);
-      guess = NS_MAX(guess, startOffset + startLength);
-
-      if (interval == 0 || guess == previousGuess) {
-        interval = 0;
-        // Our interval is empty, we've found the optimal seek point, as the
-        // start page is before the seek target, and the end page after the
-        // seek target.
-        break;
-      }
-
-      NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
-      NS_ASSERTION(guess < endOffset, "Guess must be before range end");
-      NS_ASSERTION(guess != previousGuess, "Guess should be differnt to previous");
-      previousGuess = guess;
-
-      SEEK_LOG(PR_LOG_DEBUG, ("Seek loop offset_start=%lld start_end=%lld "
-                              "offset_guess=%lld offset_end=%lld interval=%lld "
-                              "target=%lf time_start=%lld time_end=%lld",
-                              startOffset, (startOffset+startLength), guess,
-                              endOffset, interval, target, startTime, endTime));
-      hops++;
-      stream->Seek(nsISeekableStream::NS_SEEK_SET, guess);
-    
-      // We've seeked into the media somewhere. Locate the next page, and then
-      // figure out the granule time of the audio and video bitstreams there.
-      // We can then make a bisection decision based on our location in the media.
-      
-      PageSyncResult res = PageSync(&mOggState,
-                                    stream,
-                                    endOffset,
-                                    &page,
-                                    skippedBytes);
-      if (res == PAGE_SYNC_ERROR) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // We've located a page of length |ret| at |guess + skippedBytes|.
-      // Remember where the page is located.
-      pageOffset = guess + skippedBytes;
-      pageLength = page.header_len + page.body_len;
-      mPageOffset = pageOffset + pageLength;
-
-      if (mPageOffset == endOffset || res == PAGE_SYNC_END_OF_RANGE) {
-        // Our guess was too close to the end, we've ended up reading the end
-        // page. Backoff exponentially from the end point, in case the last
-        // page/frame/sample is huge.
-        backsteps = NS_MIN(backsteps + 1, maxBackStep);
-        backoff = PAGE_STEP * pow(2.0, backsteps);
-        continue;
-      }
-
-      NS_ASSERTION(mPageOffset < endOffset, "Page read cursor should be inside range");
-
-      // Read pages until we can determine the granule time of the audio and 
-      // video bitstream.
-      ogg_int64_t audioTime = -1;
-      ogg_int64_t videoTime = -1;
-      int ret;
-      oldPageOffset = mPageOffset;
-      while ((mVorbisState && audioTime == -1) ||
-             (mTheoraState && videoTime == -1)) {
-      
-        // Add the page to its codec state, determine its granule time.
-        PRUint32 serial = ogg_page_serialno(&page);
-        nsOggCodecState* codecState = nsnull;
-        mCodecStates.Get(serial, &codecState);
-        if (codecState && codecState->mActive) {
-          ret = ogg_stream_pagein(&codecState->mState, &page);
-          NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
-        }      
-
-        ogg_int64_t granulepos = ogg_page_granulepos(&page);
-
-        if (HasAudio() &&
-            granulepos != -1 &&
-            serial == mVorbisState->mSerial &&
-            audioTime == -1) {
-          audioTime = mVorbisState->Time(granulepos);
-        }
-        
-        if (HasVideo() &&
-            granulepos != -1 &&
-            serial == mTheoraState->mSerial &&
-            videoTime == -1) {
-          videoTime = mTheoraState->StartTime(granulepos);
-        }
-
-        mPageOffset += page.header_len + page.body_len;
-        if (ReadOggPage(&page) == -1) {
-          break;
-        }
-      }
-
-      if ((HasAudio() && audioTime == -1) ||
-          (HasVideo() && videoTime == -1)) 
-      {
-        backsteps = NS_MIN(backsteps + 1, maxBackStep);
-        backoff = PAGE_STEP * pow(2.0, backsteps);
-        continue;
-      }
-
-      // We've found appropriate time stamps here. Proceed to bisect
-      // the search space.
-      granuleTime = NS_MAX(audioTime, videoTime);
-      NS_ASSERTION(granuleTime > 0, "Must get a granuletime");
-      break;
-    }
-
-    if (interval == 0) {
-      // Seek termination condition; we've found the page boundary of the
-      // last page before the target, and the first page after the target.
-      SEEK_LOG(PR_LOG_DEBUG, ("Seek loop (interval == 0) break"));
-      NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
-      stream->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
-      mPageOffset = startOffset;
-      if (NS_FAILED(ResetDecode())) {
-        return NS_ERROR_FAILURE;
-      }
-      break;
-    }
-
-    SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lldms", guess, granuleTime));
-    if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
-      // We're within the fuzzy region in which we want to terminate the search.
-      stream->Seek(nsISeekableStream::NS_SEEK_SET, oldPageOffset);
-      mPageOffset = oldPageOffset;
-      if (NS_FAILED(ResetDecode())) {
-        return NS_ERROR_FAILURE;
-      }
-      break;
-    }
-
-    if (granuleTime >= seekTarget) {
-      // We've landed after the seek target.
-      ogg_int64_t old_offset_end = endOffset;
-      endOffset = pageOffset;
-      NS_ASSERTION(endOffset < old_offset_end, "offset_end must decrease");
-      endTime = granuleTime;
-    } else if (granuleTime < seekTarget) {
-      // Landed before seek target.
-      ogg_int64_t old_offset_start = startOffset;
-      startOffset = pageOffset;
-      startLength = pageLength;
-      NS_ASSERTION(startOffset > old_offset_start, "offset_start must increase");
-      startTime = granuleTime;
-    }
-    NS_ASSERTION(startTime < seekTarget, "Must be before seek target");
-    NS_ASSERTION(endTime >= seekTarget, "End must be after seek target");
-  }
-
-  SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops));
-
-  return NS_OK;
-}
-
-PRInt64 nsOggReader::ReadOggPage(ogg_page* aPage)
-{
-  NS_ASSERTION(mPlayer->OnStateMachineThread() || mPlayer->OnDecodeThread(),
-               "Should be on play state machine or decode thread.");
-  mMonitor.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;
-    }
-    // Returns a buffer that can be written too
-    // with the given size. This buffer is stored
-    // in the ogg synchronisation structure.
-    char* buffer = ogg_sync_buffer(&mOggState, 4096);
-    NS_ASSERTION(buffer, "ogg_sync_buffer failed");
-
-    // Read from the stream into the buffer
-    PRUint32 bytesRead = 0;
-
-    nsresult rv = mPlayer->mDecoder->GetCurrentStream()->Read(buffer, 4096, &bytesRead);
-    if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
-      // End of file.
-      return -1;
-    }
-
-    mPlayer->mDecoder->NotifyBytesConsumed(bytesRead);
-    // Update the synchronisation layer with the number
-    // of bytes written to the buffer
-    ret = ogg_sync_wrote(&mOggState, bytesRead);
-    NS_ENSURE_TRUE(ret == 0, -1);    
-  }
-  PRInt64 offset = mPageOffset;
-  mPageOffset += aPage->header_len + aPage->body_len;
-  
-  return offset;
-}
-
-PRBool nsOggReader::ReadOggPacket(nsOggCodecState* aCodecState,
-                                  ogg_packet* aPacket)
-{
-  NS_ASSERTION(mPlayer->OnStateMachineThread() || mPlayer->OnDecodeThread(),
-               "Should be on play state machine or decode thread.");
-  mMonitor.AssertCurrentThreadIn();
-
-  if (!aCodecState || !aCodecState->mActive) {
-    return PR_FALSE;
-  }
-
-  int ret = 0;
-  while ((ret = ogg_stream_packetout(&aCodecState->mState, aPacket)) != 1) {
-    ogg_page page;
-
-    if (aCodecState->PageInFromBuffer()) {
-      // The codec state has inserted a previously buffered page into its
-      // ogg_stream_state, no need to read a page from the channel.
-      continue;
-    }
-
-    // The codec state does not have any buffered pages, so try to read another
-    // page from the channel.
-    if (ReadOggPage(&page) == -1) {
-      return PR_FALSE;
-    }
-
-    PRUint32 serial = ogg_page_serialno(&page);
-    nsOggCodecState* codecState = nsnull;
-    mCodecStates.Get(serial, &codecState);
-
-    if (serial == aCodecState->mSerial) {
-      // This page is from our target bitstream, insert it into the
-      // codec state's ogg_stream_state so we can read a packet.
-      ret = ogg_stream_pagein(&codecState->mState, &page);
-      NS_ENSURE_TRUE(ret == 0, PR_FALSE);
-    } else if (codecState && codecState->mActive) {
-      // Page is for another active bitstream, add the page to its codec
-      // state's buffer for later consumption when that stream next tries
-      // to read a packet.
-      codecState->AddToBuffer(&page);
-    }
-  }
-
-  return PR_TRUE;
-}
-
-// Returns PR_TRUE when all bitstreams in aBitstreams array have finished
-// reading their headers.
-static PRBool DoneReadingHeaders(nsTArray<nsOggCodecState*>& aBitstreams) {
-  for (PRUint32 i = 0; i < aBitstreams .Length(); i++) {
-    if (!aBitstreams [i]->DoneReadingHeaders()) {
-      return PR_FALSE;
-    }
-  }
-  return PR_TRUE;
-}
-
-nsresult nsOggReader::ReadOggHeaders(nsOggInfo& aInfo)
-{
-  NS_ASSERTION(mPlayer->OnStateMachineThread(), "Should be on play state machine thread.");
-  MonitorAutoEnter mon(mMonitor);
-
-  // 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;
-  PRInt64 pageOffset;
-  nsAutoTArray<nsOggCodecState*,4> bitstreams;
-  PRBool readAllBOS = PR_FALSE;
-  mDataOffset = 0;
-  while (PR_TRUE) {
-    if (readAllBOS && DoneReadingHeaders(bitstreams)) {
-      if (mDataOffset == 0) {
-        // We've previously found the start of the first non-header packet.
-        mDataOffset = mPageOffset;
-      }
-      break;
-    }
-    pageOffset = ReadOggPage(&page);
-    if (pageOffset == -1) {
-      // Some kind of error...
-      break;
-    }
-
-    int ret = 0;
-    int serial = ogg_page_serialno(&page);
-    nsOggCodecState* codecState = 0;
-
-    if (ogg_page_bos(&page)) {
-      NS_ASSERTION(!readAllBOS, "We shouldn't encounter another BOS page");
-      codecState = nsOggCodecState::Create(&page);
-      PRBool r = mCodecStates.Put(serial, codecState);
-      NS_ASSERTION(r, "Failed to insert into mCodecStates");
-      bitstreams.AppendElement(codecState);
-      if (codecState &&
-          codecState->GetType() == nsOggCodecState::TYPE_VORBIS &&
-          !mVorbisState)
-      {
-        // First Vorbis bitstream, we'll play this one. Subsequent Vorbis
-        // bitstreams will be ignored.
-        mVorbisState = static_cast<nsVorbisState*>(codecState);
-      }
-      if (codecState &&
-          codecState->GetType() == nsOggCodecState::TYPE_THEORA &&
-          !mTheoraState)
-      {
-        // First Theora bitstream, we'll play this one. Subsequent Theora
-        // bitstreams will be ignored.
-        mTheoraState = static_cast<nsTheoraState*>(codecState);
-      }
-    } else {
-      // We've encountered the a non Beginning Of Stream page. No more
-      // BOS pages can follow in this Ogg segment, so there will be no other
-      // bitstreams in the Ogg (unless it's invalid).
-      readAllBOS = PR_TRUE;
-    }
-
-    mCodecStates.Get(serial, &codecState);
-    NS_ENSURE_TRUE(codecState, NS_ERROR_FAILURE);
-
-    // Add a complete page to the bitstream
-    ret = ogg_stream_pagein(&codecState->mState, &page);
-    NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
-
-    // Process all available header packets in the stream.
-    ogg_packet packet;
-    if (codecState->DoneReadingHeaders() && mDataOffset == 0)
-    {
-      // Stream has read all header packets, but now there's more data in
-      // (presumably) a non-header page, we must have finished header packets.
-      // This can happen in incorrectly chopped streams.
-      mDataOffset = pageOffset;
-      continue;
-    }
-    while (!codecState->DoneReadingHeaders() &&
-           (ret = ogg_stream_packetout(&codecState->mState, &packet)) != 0)
-    {
-      if (ret == -1) {
-        // Sync lost, we've probably encountered the continuation of a packet
-        // in a chopped video.
-        continue;
-      }
-      // A packet is available. If it is not a header packet we'll break.
-      // If it is a header packet, process it as normal.
-      codecState->DecodeHeader(&packet);
-    }
-    if (ogg_stream_packetpeek(&codecState->mState, &packet) != 0 &&
-        mDataOffset == 0)
-    {
-      // We're finished reading headers for this bitstream, but there's still
-      // packets in the bitstream to read. The bitstream is probably poorly
-      // muxed, and includes the last header packet on a page with non-header
-      // packets. We need to ensure that this is the media start page offset.
-      mDataOffset = pageOffset;
-    }
-  }
-  // Deactivate any non-primary bitstreams.
-  for (PRUint32 i = 0; i < bitstreams.Length(); i++) {
-    nsOggCodecState* s = bitstreams[i];
-    if (s != mVorbisState && s != mTheoraState) {
-      s->Deactivate();
-    }
-  }
-
-  // Initialize the first Theora and Vorbis bitstreams. According to the
-  // Theora spec these can be considered the 'primary' bitstreams for playback.
-  // Extract the metadata needed from these streams.
-  float aspectRatio = 0;
-  if (mTheoraState) {
-    if (mTheoraState->Init()) {
-      mCallbackPeriod = mTheoraState->mFrameDuration;
-      aspectRatio = mTheoraState->mAspectRatio;
-      gfxIntSize sz(mTheoraState->mInfo.pic_width,
-                    mTheoraState->mInfo.pic_height);
-      mPlayer->mDecoder->SetVideoData(sz, mTheoraState->mAspectRatio, nsnull);
-    } else {
-      mTheoraState = nsnull;
-    }
-  }
-  if (mVorbisState) {
-    mVorbisState->Init();
-  }
-
-  aInfo.mHasAudio = HasAudio();
-  aInfo.mHasVideo = HasVideo();
-  aInfo.mCallbackPeriod = mCallbackPeriod;
-  if (HasAudio()) {
-    aInfo.mAudioRate = mVorbisState->mInfo.rate;
-    aInfo.mAudioChannels = mVorbisState->mInfo.channels;
-  }
-  if (HasVideo()) {
-    aInfo.mFramerate = mTheoraState->mFrameRate;
-    aInfo.mAspectRatio = mTheoraState->mAspectRatio;
-    aInfo.mPicture.width = mTheoraState->mInfo.pic_width;
-    aInfo.mPicture.height = mTheoraState->mInfo.pic_height;
-    aInfo.mPicture.x = mTheoraState->mInfo.pic_x;
-    aInfo.mPicture.y = mTheoraState->mInfo.pic_y;
-    aInfo.mFrame.width = mTheoraState->mInfo.frame_width;
-    aInfo.mFrame.height = mTheoraState->mInfo.frame_height;
-  }
-  aInfo.mDataOffset = mDataOffset;
-
-  LOG(PR_LOG_DEBUG, ("Done loading headers, data offset %lld", mDataOffset));
-
-  return NS_OK;
-}
-
-template<class Data>
-Data* nsOggReader::DecodeToFirstData(DecodeFn aDecodeFn,
-                                     MediaQueue<Data>& aQueue)
-{
-  PRBool eof = PR_FALSE;
-  while (!eof && aQueue.GetSize() == 0) {
-    {
-      MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-      if (mPlayer->mState == nsOggPlayStateMachine::DECODER_STATE_SHUTDOWN) {
-        return nsnull;
-      }
-    }
-    eof = !(this->*aDecodeFn)();
-  }
-  Data* d = nsnull;
-  return (d = aQueue.PeekFront()) ? d : nsnull;
-}
-
-VideoData* nsOggReader::FindStartTime(PRInt64 aOffset,
+VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64 aOffset,
                                       PRInt64& aOutStartTime)
 {
-  NS_ASSERTION(mPlayer->OnStateMachineThread(), "Should be on state machine thread.");
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
 
-  nsMediaStream* stream = mPlayer->mDecoder->GetCurrentStream();
+  nsMediaStream* stream = mDecoder->GetCurrentStream();
 
   stream->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   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(&nsOggReader::DecodeVideoPage,
+    videoData = DecodeToFirstData(&nsBuiltinDecoderReader::DecodeVideoFrame,
                                   mVideoQueue);
     if (videoData) {
       videoStartTime = videoData->mTime;
     }
   }
   if (HasAudio()) {
-    SoundData* soundData = DecodeToFirstData(&nsOggReader::DecodeAudioPage,
+    SoundData* soundData = DecodeToFirstData(&nsBuiltinDecoderReader::DecodeAudioData,
                                              mAudioQueue);
     if (soundData) {
       audioStartTime = soundData->mTime;
     }
   }
 
   PRInt64 startTime = PR_MIN(videoStartTime, audioStartTime);
   if (startTime != PR_INT64_MAX) {
     aOutStartTime = startTime;
   }
 
   return videoData;
 }
 
-// Returns an ogg page's checksum.
-static ogg_uint32_t
-GetChecksum(ogg_page* page)
-{
-  if (page == 0 || page->header == 0 || page->header_len < 25) {
-    return 0;
-  }
-  const unsigned char* p = page->header + 22;
-  PRUint32 c =  p[0] +
-               (p[1] << 8) + 
-               (p[2] << 16) +
-               (p[3] << 24);
-  return c;
-}
-
-PRInt64 nsOggReader::FindEndTime(PRInt64 aEndOffset)
+template<class Data>
+Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
+                                                MediaQueue<Data>& aQueue)
 {
-  MonitorAutoEnter mon(mMonitor);
-  NS_ASSERTION(mPlayer->OnStateMachineThread(), "Should be on state machine thread.");
-
-  nsMediaStream* stream = mPlayer->mDecoder->GetCurrentStream();
-  ogg_sync_reset(&mOggState);
-
-  stream->Seek(nsISeekableStream::NS_SEEK_SET, aEndOffset);
-
-  // 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 offset = aEndOffset;
-  PRInt64 endTime = -1;
-  PRUint32 checksumAfterSeek = 0;
-  PRUint32 prevChecksumAfterSeek = 0;
-  PRBool mustBackOff = PR_FALSE;
-  while (PR_TRUE) {
+  PRBool eof = PR_FALSE;
+  while (!eof && aQueue.GetSize() == 0) {
     {
-      MonitorAutoExit exitReaderMon(mMonitor);
-      MonitorAutoEnter decoderMon(mPlayer->mDecoder->GetMonitor());
-      if (mPlayer->mState == nsOggPlayStateMachine::DECODER_STATE_SHUTDOWN) {
-        return -1;
+      MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+      if (mDecoder->GetDecodeState() == nsDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+        return nsnull;
       }
     }
-    ogg_page page;    
-    int ret = ogg_sync_pageseek(&mOggState, &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 || stream->Tell() == aEndOffset) {
-        if (endTime != -1) {
-          // 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(&mOggState);
-        offset = NS_MAX(static_cast<PRInt64>(0), offset - step);
-        stream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
-      }
-      NS_ASSERTION(stream->Tell() < aEndOffset,
-                   "Stream pos must be before range end");
-
-      PRInt64 limit = NS_MIN(static_cast<PRInt64>(PR_UINT32_MAX),
-                             aEndOffset - stream->Tell());
-      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(&mOggState,
-                                     bytesToRead);
-      NS_ASSERTION(buffer, "Must have buffer");
-      stream->Read(buffer, bytesToRead, &bytesRead);
-
-      // Update the synchronisation layer with the number
-      // of bytes written to the buffer
-      ret = ogg_sync_wrote(&mOggState, bytesRead);
-      if (ret != 0) {
-        endTime = -1;
-        break;
-      }
-
-      continue;
-    }
+    eof = !(this->*aDecodeFn)();
+  }
+  Data* d = nsnull;
+  return (d = aQueue.PeekFront()) ? d : nsnull;
+}
 
-    if (ret < 0 || ogg_page_granulepos(&page) < 0) {
-      continue;
-    }
 
-    PRUint32 checksum = GetChecksum(&page);
-    if (checksumAfterSeek == 0) {
-      // This is the first page we've decoded after a backoff/seek. Remember
-      // the page checksum. If we backoff further and encounter this page
-      // again, we'll know that we won't find a page with an end time after
-      // this one, so we'll know to back off again.
-      checksumAfterSeek = checksum;
-    }
-    if (checksum == prevChecksumAfterSeek) {
-      // This page has the same checksum as the first page we encountered
-      // after the last backoff/seek. Since we've already scanned after this
-      // page and failed to find an end time, we may as well backoff again and
-      // try to find an end time from an earlier page.
-      mustBackOff = PR_TRUE;
-      continue;
-    }
-
-    PRInt64 granulepos = ogg_page_granulepos(&page);
-    int serial = ogg_page_serialno(&page);
-
-    nsOggCodecState* codecState = nsnull;
-    mCodecStates.Get(serial, &codecState);
-
-    if (!codecState) {
-      // This page is from a bitstream which we haven't encountered yet.
-      // It's probably from a new "link" in a "chained" ogg. Don't
-      // bother even trying to find a duration...
-      break;
-    }
-
-    PRInt64 t = codecState ? codecState->Time(granulepos) : -1;
-    if (t != -1) {
-      endTime = t;
-    }
-  }
-
-  ogg_sync_reset(&mOggState);
-
-  return endTime;
-}
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -31,37 +31,28 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * 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 ***** */
-#if !defined(nsOggReader_h_)
-#define nsOggReader_h_
+#if !defined(nsBuiltinDecoderReader_h_)
+#define nsBuiltinDecoderReader_h_
 
 #include <nsDeque.h>
-#include "nsOggCodecState.h"
-#include <ogg/ogg.h>
-#include <theora/theoradec.h>
-#include <vorbis/codec.h>
 #include "nsAutoLock.h"
 #include "nsClassHashtable.h"
 #include "mozilla/TimeStamp.h"
 #include "nsSize.h"
 #include "nsRect.h"
 #include "mozilla/Monitor.h"
 
-class nsOggPlayStateMachine;
-
-using mozilla::Monitor;
-using mozilla::MonitorAutoEnter;
-using mozilla::TimeDuration;
-using mozilla::TimeStamp;
+class nsBuiltinDecoderStateMachine;
 
 // Holds chunk a decoded sound samples.
 class SoundData {
 public:
   SoundData(PRInt64 aOffset,
             PRInt64 aTime,
             PRInt64 aDuration,
             PRUint32 aSamples,
@@ -107,80 +98,98 @@ public:
 
   PRInt64 mTime; // Start time of samples in ms.
   const PRInt64 mDuration; // In ms.
   const PRUint32 mSamples;
   const PRUint32 mChannels;
   nsAutoArrayPtr<float> mAudioData;
 };
 
-// Holds a decoded Theora frame, in YCbCr format. These are queued in the reader.
+// Holds a decoded video frame, in YCbCr format. These are queued in the reader.
 class VideoData {
 public:
+  // YCbCr data obtained from decoding the video. The index's are:
+  //   0 = Y
+  //   1 = Cb
+  //   2 = Cr
+  struct YCbCrBuffer {
+    struct Plane {
+      PRUint8* mData;
+      PRUint32 mWidth;
+      PRUint32 mHeight;
+      PRUint32 mStride;
+    };
+
+    Plane mPlanes[3];
+  };
 
   // Constructs a VideoData object. Makes a copy of YCbCr data in aBuffer.
   // This may return nsnull if we run out of memory when allocating buffers
-  // to store the frame.
+  // to store the frame. aTimecode is a codec specific number representing
+  // the timestamp of the frame of video data.
   static VideoData* Create(PRInt64 aOffset,
                            PRInt64 aTime,
-                           th_ycbcr_buffer aBuffer,
+                           const YCbCrBuffer &aBuffer,
                            PRBool aKeyframe,
-                           PRInt64 aGranulepos);
+                           PRInt64 aTimecode);
 
   // Constructs a duplicate VideoData object. This intrinsically tells the
   // player that it does not need to update the displayed frame when this
   // frame is played; this frame is identical to the previous.
   static VideoData* CreateDuplicate(PRInt64 aOffset,
                                     PRInt64 aTime,
-                                    PRInt64 aGranulepos)
+                                    PRInt64 aTimecode)
   {
-    return new VideoData(aOffset, aTime, aGranulepos);
+    return new VideoData(aOffset, aTime, aTimecode);
   }
 
   ~VideoData()
   {
     MOZ_COUNT_DTOR(VideoData);
     for (PRUint32 i = 0; i < 3; ++i) {
-      delete mBuffer[i].data;
+      moz_free(mBuffer.mPlanes[i].mData);
     }
   }
 
   // Approximate byte offset of the end of the frame in the media.
   PRInt64 mOffset;
 
   // Start time of frame in milliseconds.
   PRInt64 mTime;
-  PRInt64 mGranulepos;
 
-  th_ycbcr_buffer mBuffer;
+  // Codec specific internal time code. For Ogg based codecs this is the
+  // granulepos.
+  PRInt64 mTimecode;
+
+  YCbCrBuffer mBuffer;
 
   // When PR_TRUE, denotes that this frame is identical to the frame that
   // came before; it's a duplicate. mBuffer will be empty.
   PRPackedBool mDuplicate;
   PRPackedBool mKeyframe;
 
-private:
-  VideoData(PRInt64 aOffset, PRInt64 aTime, PRInt64 aGranulepos)
+public:
+  VideoData(PRInt64 aOffset, PRInt64 aTime, PRInt64 aTimecode)
     : mOffset(aOffset),
       mTime(aTime),
-      mGranulepos(aGranulepos),
+      mTimecode(aTimecode),
       mDuplicate(PR_TRUE),
       mKeyframe(PR_FALSE)
   {
     MOZ_COUNT_CTOR(VideoData);
-    memset(&mBuffer, 0, sizeof(th_ycbcr_buffer));
+    memset(&mBuffer, 0, sizeof(YCbCrBuffer));
   }
 
   VideoData(PRInt64 aOffset,
             PRInt64 aTime,
             PRBool aKeyframe,
-            PRInt64 aGranulepos)
+            PRInt64 aTimecode)
     : mOffset(aOffset),
       mTime(aTime),
-      mGranulepos(aGranulepos),
+      mTimecode(aTimecode),
       mDuplicate(PR_FALSE),
       mKeyframe(aKeyframe)
   {
     MOZ_COUNT_CTOR(VideoData);
   }
 
 };
 
@@ -190,17 +199,19 @@ class MediaQueueDeallocator : public nsD
   virtual void* operator() (void* anObject) {
     delete static_cast<T*>(anObject);
     return nsnull;
   }
 };
 
 template <class T> class MediaQueue : private nsDeque {
  public:
-  
+   typedef mozilla::MonitorAutoEnter MonitorAutoEnter;
+   typedef mozilla::Monitor Monitor;
+
    MediaQueue()
      : nsDeque(new MediaQueueDeallocator<T>()),
        mMonitor("mediaqueue"),
        mEndOfStream(0)
    {}
   
   ~MediaQueue() {
     Reset();
@@ -279,18 +290,18 @@ template <class T> class MediaQueue : pr
     T* last = Peek();
     T* first = PeekFront();
     return last->mTime - first->mTime;
   }
 
 private:
   Monitor mMonitor;
 
-  // PR_TRUE when we've decoded the last packet in the bitstream for which
-  // we're queueing sample-data.
+  // PR_TRUE when we've decoded the last frame of data in the
+  // bitstream for which we're queueing sample-data.
   PRBool mEndOfStream;
 };
 
 // Represents a section of contiguous media, with a start and end offset,
 // and the timestamps of the start and end of that range. Used to denote the
 // extremities of a range to seek in.
 class ByteRange {
 public:
@@ -318,33 +329,33 @@ public:
            mTimeEnd == 0;
   }
 
   PRInt64 mOffsetStart, mOffsetEnd; // in bytes.
   PRInt64 mTimeStart, mTimeEnd; // in ms.
 };
 
 // Stores info relevant to presenting media samples.
-class nsOggInfo {
+class nsVideoInfo {
 public:
-  nsOggInfo()
+  nsVideoInfo()
     : mFramerate(0.0),
       mAspectRatio(1.0),
       mCallbackPeriod(1),
       mAudioRate(0),
       mAudioChannels(0),
       mFrame(0,0),
       mHasAudio(PR_FALSE),
       mHasVideo(PR_FALSE)
   {}
 
   // Frames per second.
   float mFramerate;
 
-  // Aspect ratio, as stored in the video header packet.
+  // Aspect ratio, as stored in the metadata.
   float mAspectRatio;
 
   // Length of a video frame in milliseconds, or the callback period if
   // there's no audio.
   PRUint32 mCallbackPeriod;
 
   // Samples per second.
   PRUint32 mAudioRate;
@@ -364,131 +375,95 @@ public:
 
   // PR_TRUE if we have an active audio bitstream.
   PRPackedBool mHasAudio;
 
   // PR_TRUE if we have an active video bitstream.
   PRPackedBool mHasVideo;
 };
 
-// Encapsulates the decoding and reading of Ogg data. Reading can be done
+// 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 nsOggDecoder's monitor when calling into
+// and correctness. Never hold the nsBuiltinDecoder's monitor when calling into
 // this class.
-class nsOggReader : public nsRunnable {
+class nsBuiltinDecoderReader : public nsRunnable {
 public:
-  nsOggReader(nsOggPlayStateMachine* aStateMachine);
-  ~nsOggReader();
+  typedef mozilla::Monitor Monitor;
+
+  nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder);
+  ~nsBuiltinDecoderReader();
+
+  // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
+  // on failure.
+  virtual nsresult Init() = 0;
+
+  // Resets all state related to decoding, emptying all buffers etc.
+  virtual nsresult ResetDecode();
 
-  PRBool HasAudio()
-  {
-    MonitorAutoEnter mon(mMonitor);
-    return mVorbisState != 0 && mVorbisState->mActive;
-  }
+  // Decodes an unspecified amount of audio data, enqueuing the audio data
+  // in mAudioQueue. Returns PR_TRUE when there's more audio to decode,
+  // PR_FALSE if the audio is finished, end of file has been reached,
+  // or an un-recoverable read error has occured.
+  virtual PRBool DecodeAudioData() = 0;
 
-  PRBool HasVideo()
-  {
-    MonitorAutoEnter mon(mMonitor);
-    return mTheoraState != 0 && mTheoraState->mActive;
-  }
+  // Reads and decodes one video frame. Packets with a timestamp less
+  // than aTimeThreshold will be decoded (unless they're not keyframes
+  // and aKeyframeSkip is PR_TRUE), but will not be added to the queue.
+  virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
+                                  PRInt64 aTimeThreshold) = 0;
 
-  // Read header data for all bitstreams in the Ogg file. Fills aInfo with
+  virtual PRBool HasAudio() = 0;
+  virtual PRBool HasVideo() = 0;
+
+  // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media. Returns NS_OK on success,
   // or NS_ERROR_FAILURE on failure.
-  nsresult ReadOggHeaders(nsOggInfo& aInfo);
+  virtual nsresult ReadMetadata(nsVideoInfo& aInfo) = 0;
+
 
   // Stores the presentation time of the first sample in the stream in
   // aOutStartTime, and returns the first video sample, if we have video.
   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.
-  PRInt64 FindEndTime(PRInt64 aEndOffset);
-
-  // Decodes one Vorbis page, enqueuing the audio data in mAudioQueue.
-  // Returns PR_TRUE when there's more audio to decode, PR_FALSE if the
-  // audio is finished, end of file has been reached, or an un-recoverable
-  // read error has occured.
-  PRBool DecodeAudioPage();
-  
-  // Reads and decodes one video frame. 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.
-  // Packets with a timestamp less than aTimeThreshold will be decoded (unless
-  // they're not keyframes and aKeyframeSkip is PR_TRUE), but will not be
-  // added to the queue.
-  PRBool DecodeVideoPage(PRBool &aKeyframeSkip,
-                         PRInt64 aTimeThreshold);
+  virtual PRInt64 FindEndTime(PRInt64 aEndOffset) = 0;
 
   // Moves the decode head to aTime milliseconds. aStartTime and aEndTime
   // denote the start and end times of the media.
-  nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime);
+  virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime) = 0;
 
   // Queue of audio samples. This queue is threadsafe.
   MediaQueue<SoundData> mAudioQueue;
 
   // Queue of video samples. This queue is threadsafe.
   MediaQueue<VideoData> mVideoQueue;
 
-  // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
-  // on failure.
-  nsresult Init();
+protected:
 
-private:
-
-  // Ogg reader decode function. Matches DecodeVideoPage() and
-  // DecodeAudioPage().
-  typedef PRBool (nsOggReader::*DecodeFn)();
+  // Reader decode function. Matches DecodeVideoFrame() and
+  // DecodeAudioData().
+  typedef PRBool (nsBuiltinDecoderReader::*DecodeFn)();
 
   // Calls aDecodeFn on *this until aQueue has a sample, whereupon
   // we return the first sample.
   template<class Data>
   Data* DecodeToFirstData(DecodeFn aDecodeFn,
                           MediaQueue<Data>& aQueue);
 
-  // Wrapper so that DecodeVideoPage(PRBool&,PRInt64) can be called from
+  // Wrapper so that DecodeVideoFrame(PRBool&,PRInt64) can be called from
   // DecodeToFirstData().
-  PRBool DecodeVideoPage() {
+  PRBool DecodeVideoFrame() {
     PRBool f = PR_FALSE;
-    return DecodeVideoPage(f, 0);
+    return DecodeVideoFrame(f, 0);
   }
 
-  // Decodes one packet of Vorbis data, storing the resulting chunks of
-  // PCM samples in aChunks.
-  nsresult DecodeVorbis(nsTArray<SoundData*>& aChunks,
-                        ogg_packet* aPacket);
-
-  // May return NS_ERROR_OUT_OF_MEMORY.
-  nsresult DecodeTheora(nsTArray<VideoData*>& aFrames,
-                        ogg_packet* aPacket);
-
-  // Resets all state related to decoding, emptying all buffers etc.
-  nsresult ResetDecode();
-
-  // Read a page of data from the Ogg file. Returns the offset of the start
-  // of the page, or -1 if the page read failed.
-  PRInt64 ReadOggPage(ogg_page* aPage);
-
-  // Read a packet for an Ogg bitstream/codec state. Returns PR_TRUE on
-  // success, or PR_FALSE if the read failed.
-  PRBool ReadOggPacket(nsOggCodecState* aCodecState, ogg_packet* aPacket);
-
-  // Performs a seek bisection to move the media stream's read cursor to the
-  // last ogg page boundary which has end time before aTarget ms 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 ms of leniency we'll allow; we'll terminate the
-  // seek when we land in the range (aTime - aFuzz, aTime) ms.
-  nsresult SeekBisection(PRInt64 aTarget,
-                         const ByteRange& aRange,
-                         PRUint32 aFuzz);
-
   // Fills aRanges with ByteRanges denoting the sections of the media which
   // have been downloaded and are stored in the media cache. The reader
   // monitor must must be held with exactly one lock count. The nsMediaStream
   // must be pinned while calling this.
   nsresult GetBufferedBytes(nsTArray<ByteRange>& aRanges);
 
   // Returns the range in which you should perform a seek bisection if
   // you wish to seek to aTarget ms, given the known (buffered) byte ranges
@@ -503,44 +478,18 @@ private:
                          PRInt64 aStartTime,
                          PRInt64 aEndTime,
                          PRBool aExact);
 
   // The lock which we hold whenever we read or decode. This ensures the thread
   // safety of the reader and its data fields.
   Monitor mMonitor;
 
-  // Reference to the owning player state machine object. Do not hold the
-  // reader's monitor when accessing the player.
-  nsOggPlayStateMachine* mPlayer;
-
-  // Maps Ogg serialnos to nsOggStreams.
-  nsClassHashtable<nsUint32HashKey, nsOggCodecState> mCodecStates;
-
-  // Decode state of the Theora bitstream we're decoding, if we have video.
-  nsTheoraState* mTheoraState;
-
-  // Decode state of the Vorbis bitstream we're decoding, if we have audio.
-  nsVorbisState* mVorbisState;
-
-  // Ogg decoding state.
-  ogg_sync_state mOggState;
-
-  // The offset of the end of the last page we've read, or the start of
-  // the page we're about to read.
-  PRInt64 mPageOffset;
+  // Reference to the owning decoder object. Do not hold the
+  // reader's monitor when accessing this.
+  nsBuiltinDecoder* mDecoder;
 
   // The offset of the start of the first non-header page in the file.
   // Used to seek to media start time.
   PRInt64 mDataOffset;
-
-  // The granulepos of the last decoded Theora frame.
-  PRInt64 mTheoraGranulepos;
-
-  // The granulepos of the last decoded Vorbis sample.
-  PRInt64 mVorbisGranulepos;
-
-  // Number of milliseconds of data video/audio data held in a frame.
-  PRUint32 mCallbackPeriod;
-
 };
 
 #endif
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -36,23 +36,23 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <limits>
 #include "nsAudioStream.h"
 #include "nsTArray.h"
 #include "nsBuiltinDecoder.h"
-#include "nsOggReader.h"
-#include "nsOggPlayStateMachine.h"
+#include "nsBuiltinDecoderReader.h"
+#include "nsBuiltinDecoderStateMachine.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 
+using namespace mozilla;
 using namespace mozilla::layers;
-using mozilla::MonitorAutoExit;
 
 // Adds two 32bit unsigned numbers, retuns PR_TRUE if addition succeeded,
 // or PR_FALSE the if addition would result in an overflow.
 static PRBool AddOverflow(PRUint32 a, PRUint32 b, PRUint32& aResult);
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gBuiltinDecoderLog;
 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
@@ -67,20 +67,16 @@ extern PRLogModuleInfo* gBuiltinDecoderL
 
 // The amount of data to retrieve during buffering is computed based
 // on the download rate. BUFFERING_MIN_RATE is the minimum download
 // rate to be used in that calculation to help avoid constant buffering
 // attempts at a time when the average download rate has not stabilised.
 #define BUFFERING_MIN_RATE 50000
 #define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
 
-// The frame rate to use if there is no video data in the resource to
-// be played.
-#define AUDIO_FRAME_RATE 25.0
-
 // If audio queue has less than this many ms of decoded audio, we won't risk
 // trying to decode the video, we'll skip decoding video up to the next
 // keyframe.
 //
 // Also if the decode catches up with the end of the downloaded data,
 // we'll only go into BUFFERING state if we've got audio and have queued
 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
 // less than LOW_VIDEO_FRAMES frames.
@@ -91,47 +87,53 @@ static const PRUint32 LOW_AUDIO_MS = 100
 // which is at or after the current playback position.
 //
 // Also if the decode catches up with the end of the downloaded data,
 // we'll only go into BUFFERING state if we've got audio and have queued
 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
 // less than LOW_VIDEO_FRAMES frames.
 static const PRUint32 LOW_VIDEO_FRAMES = 1;
 
-nsOggPlayStateMachine::nsOggPlayStateMachine(nsBuiltinDecoder* aDecoder) :
+// The frame rate to use if there is no video data in the resource to
+// be played.
+#define AUDIO_FRAME_RATE 25.0
+
+nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder,
+                                                           nsBuiltinDecoderReader* aReader) :
   mDecoder(aDecoder),
   mState(DECODER_STATE_DECODING_METADATA),
   mAudioMonitor("media.audiostream"),
   mCbCrSize(0),
   mPlayDuration(0),
   mBufferingEndOffset(0),
   mStartTime(-1),
   mEndTime(-1),
   mSeekTime(0),
+  mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mVideoFrameTime(-1),
   mVolume(1.0),
   mSeekable(PR_TRUE),
   mPositionChangeQueued(PR_FALSE),
   mAudioCompleted(PR_FALSE),
   mBufferExhausted(PR_FALSE),
   mGotDurationFromHeader(PR_FALSE),
   mStopDecodeThreads(PR_TRUE)
 {
-  MOZ_COUNT_CTOR(nsOggPlayStateMachine);
+  MOZ_COUNT_CTOR(nsBuiltinDecoderStateMachine);
 }
 
-nsOggPlayStateMachine::~nsOggPlayStateMachine()
+nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
 {
-  MOZ_COUNT_DTOR(nsOggPlayStateMachine);
+  MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
 }
 
-void nsOggPlayStateMachine::DecodeLoop()
+void nsBuiltinDecoderStateMachine::DecodeLoop()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   PRBool videoPlaying = PR_FALSE;
   PRBool audioPlaying = PR_FALSE;
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     videoPlaying = HasVideo();
     audioPlaying = HasAudio();
@@ -227,33 +229,33 @@ void nsOggPlayStateMachine::DecodeLoop()
     if (audioPump && audioDecoded > audioPumpThresholdMs) {
       audioPump = PR_FALSE;
     }
     if (!audioPump && audioPlaying && audioDecoded < LOW_AUDIO_MS) {
       skipToNextKeyframe = PR_TRUE;
     }
 
     if (videoPlaying && !videoWait) {
-      videoPlaying = mReader->DecodeVideoPage(skipToNextKeyframe, currentTime);
+      videoPlaying = mReader->DecodeVideoFrame(skipToNextKeyframe, currentTime);
       {
         MonitorAutoEnter mon(mDecoder->GetMonitor());
         if (mDecoder->mDecoderPosition >= initialDownloadPosition) {
           mBufferExhausted = PR_TRUE;
         }
       }
     }
     {
       MonitorAutoEnter mon(mDecoder->GetMonitor());
       initialDownloadPosition =
         mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
       mDecoder->GetMonitor().NotifyAll();
     }
 
     if (audioPlaying && !audioWait) {
-      audioPlaying = mReader->DecodeAudioPage();
+      audioPlaying = mReader->DecodeAudioData();
       {
         MonitorAutoEnter mon(mDecoder->GetMonitor());
         if (mDecoder->mDecoderPosition >= initialDownloadPosition) {
           mBufferExhausted = PR_TRUE;
         }
       }
     }
 
@@ -295,24 +297,24 @@ void nsOggPlayStateMachine::DecodeLoop()
     {
       mState = DECODER_STATE_COMPLETED;
       mDecoder->GetMonitor().NotifyAll();
     }
   }
   LOG(PR_LOG_DEBUG, ("Shutting down DecodeLoop this=%p", this));
 }
 
-PRBool nsOggPlayStateMachine::IsPlaying()
+PRBool nsBuiltinDecoderStateMachine::IsPlaying()
 {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   return !mPlayStartTime.IsNull();
 }
 
-void nsOggPlayStateMachine::AudioLoop()
+void nsBuiltinDecoderStateMachine::AudioLoop()
 {
   NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
   LOG(PR_LOG_DEBUG, ("Begun audio thread/loop"));
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     mAudioCompleted = PR_FALSE;
   }
   PRInt64 audioStartTime = -1;
@@ -399,23 +401,22 @@ void nsOggPlayStateMachine::AudioLoop()
   }
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     mAudioCompleted = PR_TRUE;
   }
   LOG(PR_LOG_DEBUG, ("Audio stream finished playing, audio thread exit"));
 }
 
-nsresult nsOggPlayStateMachine::Init()
+nsresult nsBuiltinDecoderStateMachine::Init()
 {
-  mReader = new nsOggReader(this);
   return mReader->Init();
 }
 
-void nsOggPlayStateMachine::StopPlayback(eStopMode aMode)
+void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   // 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
@@ -434,17 +435,17 @@ void nsOggPlayStateMachine::StopPlayback
       } else if (aMode == AUDIO_SHUTDOWN) {
         mAudioStream->Shutdown();
         mAudioStream = nsnull;
       }
     }
   }
 }
 
-void nsOggPlayStateMachine::StartPlayback()
+void nsBuiltinDecoderStateMachine::StartPlayback()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
   LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder));
   if (HasAudio()) {
     MonitorAutoExit exitMon(mDecoder->GetMonitor());
@@ -461,17 +462,17 @@ void nsOggPlayStateMachine::StartPlaybac
                          nsAudioStream::FORMAT_FLOAT32);
       mAudioStream->SetVolume(mVolume);
     }
   }
   mPlayStartTime = TimeStamp::Now();
   mDecoder->GetMonitor().NotifyAll();
 }
 
-void nsOggPlayStateMachine::UpdatePlaybackPosition(PRInt64 aTime)
+void nsBuiltinDecoderStateMachine::UpdatePlaybackPosition(PRInt64 aTime)
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
   mCurrentFrameTime = aTime - mStartTime;
   NS_ASSERTION(mCurrentFrameTime >= 0, "CurrentTime should be positive!");
@@ -486,126 +487,126 @@ void nsOggPlayStateMachine::UpdatePlayba
   if (!mPositionChangeQueued) {
     mPositionChangeQueued = PR_TRUE;
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackPositionChanged);
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
 }
 
-void nsOggPlayStateMachine::ClearPositionChangeFlag()
+void nsBuiltinDecoderStateMachine::ClearPositionChangeFlag()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   mPositionChangeQueued = PR_FALSE;
 }
 
-nsHTMLMediaElement::NextFrameStatus nsOggPlayStateMachine::GetNextFrameStatus()
+nsHTMLMediaElement::NextFrameStatus nsBuiltinDecoderStateMachine::GetNextFrameStatus()
 {
   MonitorAutoEnter mon(mDecoder->GetMonitor());
   if (IsBuffering() || IsSeeking()) {
     return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
   } else if (HaveNextFrameData()) {
     return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
   }
   return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
 }
 
-void nsOggPlayStateMachine::SetVolume(float volume)
+void nsBuiltinDecoderStateMachine::SetVolume(float volume)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   {
     MonitorAutoEnter audioMon(mAudioMonitor);
     if (mAudioStream) {
       mAudioStream->SetVolume(volume);
     }
   }
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     mVolume = volume;
   }
 }
 
-float nsOggPlayStateMachine::GetCurrentTime()
+float nsBuiltinDecoderStateMachine::GetCurrentTime()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   return (float)mCurrentFrameTime / 1000.0;
 }
 
-PRInt64 nsOggPlayStateMachine::GetDuration()
+PRInt64 nsBuiltinDecoderStateMachine::GetDuration()
 {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   if (mEndTime == -1 || mStartTime == -1)
     return -1;
   return mEndTime - mStartTime;
 }
 
-void nsOggPlayStateMachine::SetDuration(PRInt64 aDuration)
+void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   if (mStartTime != -1) {
     mEndTime = mStartTime + aDuration;
   } else {
     mStartTime = 0;
     mEndTime = aDuration;
   }
 }
 
-void nsOggPlayStateMachine::SetSeekable(PRBool aSeekable)
+void nsBuiltinDecoderStateMachine::SetSeekable(PRBool aSeekable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   mSeekable = aSeekable;
 }
 
-void nsOggPlayStateMachine::Shutdown()
+void nsBuiltinDecoderStateMachine::Shutdown()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Once we've entered the shutdown state here there's no going back.
   MonitorAutoEnter mon(mDecoder->GetMonitor());
 
   // 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));
   mState = DECODER_STATE_SHUTDOWN;
   mDecoder->GetMonitor().NotifyAll();
 }
 
-void nsOggPlayStateMachine::Decode()
+void nsBuiltinDecoderStateMachine::Decode()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   // When asked to decode, switch to decoding only if
   // we are currently buffering.
   MonitorAutoEnter mon(mDecoder->GetMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
     LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
     mState = DECODER_STATE_DECODING;
     mDecoder->GetMonitor().NotifyAll();
   }
 }
 
-void nsOggPlayStateMachine::ResetPlayback()
+void nsBuiltinDecoderStateMachine::ResetPlayback()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mVideoFrameTime = -1;
   mAudioStartTime = -1;
   mAudioEndTime = -1;
   mAudioCompleted = PR_FALSE;
 }
 
-void nsOggPlayStateMachine::Seek(float aTime)
+void nsBuiltinDecoderStateMachine::Seek(float aTime)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   MonitorAutoEnter mon(mDecoder->GetMonitor());
   // nsBuiltinDecoder::mPlayState should be SEEKING while we seek, and
   // in that case nsBuiltinDecoder shouldn't be calling us.
   NS_ASSERTION(mState != DECODER_STATE_SEEKING,
                "We shouldn't already be seeking");
   NS_ASSERTION(mState >= DECODER_STATE_DECODING,
@@ -624,17 +625,17 @@ void nsOggPlayStateMachine::Seek(float a
   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, aTime));
   mState = DECODER_STATE_SEEKING;
 }
 
-void nsOggPlayStateMachine::StopDecodeThreads()
+void nsBuiltinDecoderStateMachine::StopDecodeThreads()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
   mStopDecodeThreads = PR_TRUE;
   mDecoder->GetMonitor().NotifyAll();
   if (mDecodeThread) {
     {
@@ -648,46 +649,46 @@ void nsOggPlayStateMachine::StopDecodeTh
       MonitorAutoExit exitMon(mDecoder->GetMonitor());
       mAudioThread->Shutdown();
     }
     mAudioThread = nsnull;
   }
 }
 
 nsresult
-nsOggPlayStateMachine::StartDecodeThreads()
+nsBuiltinDecoderStateMachine::StartDecodeThreads()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
   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, &nsOggPlayStateMachine::DecodeLoop);
+      NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::DecodeLoop);
     mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
   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, &nsOggPlayStateMachine::AudioLoop);
+      NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::AudioLoop);
     mAudioThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
   return NS_OK;
 }
 
-nsresult nsOggPlayStateMachine::Run()
+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) {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
@@ -698,17 +699,17 @@ nsresult nsOggPlayStateMachine::Run()
       }
       StopDecodeThreads();
       NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
                    "How did we escape from the shutdown state???");
       return NS_OK;
 
     case DECODER_STATE_DECODING_METADATA:
       {
-        LoadOggHeaders();
+        LoadMetadata();
         if (mState == DECODER_STATE_SHUTDOWN) {
           continue;
         }
 
         VideoData* videoData = FindStartTime();
         if (videoData) {
           MonitorAutoExit exitMon(mDecoder->GetMonitor());
           RenderVideoFrame(videoData);
@@ -726,17 +727,17 @@ nsresult nsOggPlayStateMachine::Run()
                      "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 %lldms to %lldms (duration %lldms) seekable=%d",
                            mDecoder, mStartTime, mEndTime, GetDuration(), mSeekable));
 
         if (mState == DECODER_STATE_SHUTDOWN)
           continue;
 
-        // Inform the element that we've loaded the Ogg metadata and the
+        // Inform the element that we've loaded the metadata and the
         // first frame.
         nsCOMPtr<nsIRunnable> metadataLoadedEvent =
           NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::MetadataLoaded);
         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));
           mState = DECODER_STATE_DECODING;
@@ -865,17 +866,17 @@ nsresult nsOggPlayStateMachine::Run()
         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, ("Seek completed, mCurrentFrameTime=%lld\n", mCurrentFrameTime));
 
         // Change state to DECODING or COMPLETED now. SeekingStopped will
-        // call nsOggPlayStateMachine::Seek to reset our state to SEEKING
+        // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
         // if we need to seek again.
         
         nsCOMPtr<nsIRunnable> stopEvent;
         if (mCurrentFrameTime == mEndTime) {
           LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lldms) to COMPLETED",
                              mDecoder, seekTime));
           stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd);
           mState = DECODER_STATE_COMPLETED;
@@ -977,48 +978,48 @@ nsresult nsOggPlayStateMachine::Run()
       }
       break;
     }
   }
 
   return NS_OK;
 }
 
-void nsOggPlayStateMachine::RenderVideoFrame(VideoData* aData)
+void nsBuiltinDecoderStateMachine::RenderVideoFrame(VideoData* aData)
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
 
   if (aData->mDuplicate) {
     return;
   }
 
   NS_ASSERTION(mInfo.mPicture.width != 0 && mInfo.mPicture.height != 0,
                "We can only render non-zero-sized video");
-  NS_ASSERTION(aData->mBuffer[0].stride >= 0 && aData->mBuffer[0].height >= 0 &&
-               aData->mBuffer[1].stride >= 0 && aData->mBuffer[1].height >= 0 &&
-               aData->mBuffer[2].stride >= 0 && aData->mBuffer[2].height >= 0,
+  NS_ASSERTION(aData->mBuffer.mPlanes[0].mStride >= 0 && aData->mBuffer.mPlanes[0].mHeight >= 0 &&
+               aData->mBuffer.mPlanes[1].mStride >= 0 && aData->mBuffer.mPlanes[1].mHeight >= 0 &&
+               aData->mBuffer.mPlanes[2].mStride >= 0 && aData->mBuffer.mPlanes[2].mHeight >= 0,
                "YCbCr stride and height must be non-negative");
 
   // Ensure the picture size specified in the headers can be extracted out of
   // the frame we've been supplied without indexing out of bounds.
   PRUint32 picXLimit;
   PRUint32 picYLimit;
   if (!AddOverflow(mInfo.mPicture.x, mInfo.mPicture.width, picXLimit) ||
-      picXLimit > PRUint32(PR_ABS(aData->mBuffer[0].stride)) ||
+      picXLimit > PRUint32(PR_ABS(aData->mBuffer.mPlanes[0].mStride)) ||
       !AddOverflow(mInfo.mPicture.y, mInfo.mPicture.height, picYLimit) ||
-      picYLimit > PRUint32(PR_ABS(aData->mBuffer[0].height)))
+      picYLimit > PRUint32(PR_ABS(aData->mBuffer.mPlanes[0].mHeight)))
   {
     // The specified picture dimensions can't be contained inside the video
     // frame, we'll stomp memory if we try to copy it. Fail.
     return;
   }
 
-  unsigned ySize = aData->mBuffer[0].stride * aData->mBuffer[0].height;
-  unsigned cbSize = aData->mBuffer[1].stride * aData->mBuffer[1].height;
-  unsigned crSize = aData->mBuffer[2].stride * aData->mBuffer[2].height;
+  unsigned ySize = aData->mBuffer.mPlanes[0].mStride * aData->mBuffer.mPlanes[0].mHeight;
+  unsigned cbSize = aData->mBuffer.mPlanes[1].mStride * aData->mBuffer.mPlanes[1].mHeight;
+  unsigned crSize = aData->mBuffer.mPlanes[2].mStride * aData->mBuffer.mPlanes[2].mHeight;
   unsigned cbCrSize = ySize + cbSize + crSize;
 
   if (cbCrSize != mCbCrSize) {
     mCbCrSize = cbCrSize;
     mCbCrBuffer = static_cast<unsigned char*>(moz_xmalloc(cbCrSize));
     if (!mCbCrBuffer) {
       // Malloc failed...
       NS_WARNING("Malloc failure allocating YCbCr->RGB buffer");
@@ -1027,59 +1028,59 @@ void nsOggPlayStateMachine::RenderVideoF
   }
 
   unsigned char* data = mCbCrBuffer.get();
 
   unsigned char* y = data;
   unsigned char* cb = y + ySize;
   unsigned char* cr = cb + cbSize;
   
-  memcpy(y, aData->mBuffer[0].data, ySize);
-  memcpy(cb, aData->mBuffer[1].data, cbSize);
-  memcpy(cr, aData->mBuffer[2].data, crSize);
+  memcpy(y, aData->mBuffer.mPlanes[0].mData, ySize);
+  memcpy(cb, aData->mBuffer.mPlanes[1].mData, cbSize);
+  memcpy(cr, aData->mBuffer.mPlanes[2].mData, crSize);
  
   ImageContainer* container = mDecoder->GetImageContainer();
-  // Currently our Ogg decoder only knows how to output to PLANAR_YCBCR
+  // Currently our decoder only knows how to output to PLANAR_YCBCR
   // format.
   Image::Format format = Image::PLANAR_YCBCR;
   nsRefPtr<Image> image;
   if (container) {
     image = container->CreateImage(&format, 1);
   }
   if (image) {
     NS_ASSERTION(image->GetFormat() == Image::PLANAR_YCBCR,
                  "Wrong format?");
     PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(image.get());
     PlanarYCbCrImage::Data data;
     data.mYChannel = y;
     data.mYSize = gfxIntSize(mInfo.mFrame.width, mInfo.mFrame.height);
-    data.mYStride = aData->mBuffer[0].stride;
+    data.mYStride = aData->mBuffer.mPlanes[0].mStride;
     data.mCbChannel = cb;
     data.mCrChannel = cr;
-    data.mCbCrSize = gfxIntSize(aData->mBuffer[1].width, aData->mBuffer[1].height);
-    data.mCbCrStride = aData->mBuffer[1].stride;
+    data.mCbCrSize = gfxIntSize(aData->mBuffer.mPlanes[1].mWidth, aData->mBuffer.mPlanes[1].mHeight);
+    data.mCbCrStride = aData->mBuffer.mPlanes[1].mStride;
     data.mPicX = mInfo.mPicture.x;
     data.mPicY = mInfo.mPicture.y;
     data.mPicSize = gfxIntSize(mInfo.mPicture.width, mInfo.mPicture.height);
     videoImage->SetData(data);
     mDecoder->SetVideoData(data.mPicSize, mInfo.mAspectRatio, image);
   }
 }
 
 PRInt64
-nsOggPlayStateMachine::GetAudioClock()
+nsBuiltinDecoderStateMachine::GetAudioClock()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
   if (!mAudioStream || !HasAudio())
     return -1;
   PRInt64 t = mAudioStream->GetPosition();
   return (t == -1) ? -1 : t + mAudioStartTime;
 }
 
-void nsOggPlayStateMachine::AdvanceFrame()
+void nsBuiltinDecoderStateMachine::AdvanceFrame()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   // When it's time to display a frame, decode the frame and display it.
   if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
     if (!IsPlaying()) {
       StartPlayback();
@@ -1172,87 +1173,36 @@ void nsOggPlayStateMachine::AdvanceFrame
 
     if (mState == DECODER_STATE_DECODING ||
         mState == DECODER_STATE_COMPLETED) {
       mDecoder->GetMonitor().Wait();
     }
   }
 }
 
-void nsOggPlayStateMachine::Wait(PRUint32 aMs) {
+void nsBuiltinDecoderStateMachine::Wait(PRUint32 aMs) {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
   TimeStamp end = TimeStamp::Now() + TimeDuration::FromMilliseconds(aMs);
   TimeStamp now;
   while ((now = TimeStamp::Now()) < end &&
          mState != DECODER_STATE_SHUTDOWN &&
          mState != DECODER_STATE_SEEKING)
   {
     TimeDuration d = end - now;
     PRInt64 ms = d.ToSeconds() * 1000;
     if (ms == 0) {
       break;
     }
     NS_ASSERTION(ms <= aMs && ms > 0,
-                 "nsOggPlayStateMachine::Wait interval very wrong!");
+                 "nsBuiltinDecoderStateMachine::Wait interval very wrong!");
     mDecoder->GetMonitor().Wait(PR_MillisecondsToInterval(ms));
   }
 }
 
-void nsOggPlayStateMachine::LoadOggHeaders()
-{
-  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
-               "Should be on state machine thread.");
-  mDecoder->GetMonitor().AssertCurrentThreadIn();
-
-  LOG(PR_LOG_DEBUG, ("Loading Ogg Headers"));
-
-  nsMediaStream* stream = mDecoder->mStream;
-
-  nsOggInfo info;
-  {
-    MonitorAutoExit exitMon(mDecoder->GetMonitor());
-    mReader->ReadOggHeaders(info);
-  }
-  mInfo = info;
-  mDecoder->StartProgressUpdates();
-
-  if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
-    mState = DECODER_STATE_SHUTDOWN;      
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DecodeError);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-    return;
-  }
-
-  if (!mInfo.mHasVideo) {
-    mInfo.mCallbackPeriod = 1000 / AUDIO_FRAME_RATE;
-  }
-  LOG(PR_LOG_DEBUG, ("%p Callback Period: %u", mDecoder, mInfo.mCallbackPeriod));
-
-  // TODO: Get the duration from Skeleton index, if available.
-
-  // Get the duration from the Ogg 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.
-  mGotDurationFromHeader = (GetDuration() != -1);
-  if (mState != DECODER_STATE_SHUTDOWN &&
-      stream->GetLength() >= 0 &&
-      mSeekable &&
-      mEndTime == -1)
-  {
-    mDecoder->StopProgressUpdates();
-    FindEndTime();
-    mDecoder->StartProgressUpdates();
-    mDecoder->UpdatePlaybackRate();
-  }
-}
-
-VideoData* nsOggPlayStateMachine::FindStartTime()
+VideoData* nsBuiltinDecoderStateMachine::FindStartTime()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
   PRInt64 startTime = 0;
   mStartTime = 0;
   VideoData* v = nsnull;
   {
     MonitorAutoExit exitMon(mDecoder->GetMonitor());
@@ -1268,17 +1218,17 @@ VideoData* nsOggPlayStateMachine::FindSt
       // duration.
       mEndTime = mStartTime + mEndTime;
     }
   }
   LOG(PR_LOG_DEBUG, ("%p Media start time is %lldms", mDecoder, mStartTime));
   return v;
 }
 
-void nsOggPlayStateMachine::FindEndTime() 
+void nsBuiltinDecoderStateMachine::FindEndTime() 
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   nsMediaStream* stream = mDecoder->GetCurrentStream();
 
   // Seek to the end of file to find the length and duration.
   PRInt64 length = stream->GetLength();
@@ -1298,17 +1248,17 @@ void nsOggPlayStateMachine::FindEndTime(
                "Should have offset of first non-header page");
   {
     MonitorAutoExit exitMon(mDecoder->GetMonitor());
     stream->Seek(nsISeekableStream::NS_SEEK_SET, mInfo.mDataOffset);
   }
   LOG(PR_LOG_DEBUG, ("%p Media end time is %lldms", mDecoder, mEndTime));   
 }
 
-void nsOggPlayStateMachine::UpdateReadyState() {
+void nsBuiltinDecoderStateMachine::UpdateReadyState() {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   nsCOMPtr<nsIRunnable> event;
   switch (GetNextFrameStatus()) {
     case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING:
       event = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::NextFrameUnavailableBuffering);
       break;
     case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE:
@@ -1328,8 +1278,59 @@ void nsOggPlayStateMachine::UpdateReadyS
 static PRBool AddOverflow(PRUint32 a, PRUint32 b, PRUint32& aResult) {
   PRUint64 rl = static_cast<PRUint64>(a) + static_cast<PRUint64>(b);
   if (rl > PR_UINT32_MAX) {
     return PR_FALSE;
   }
   aResult = static_cast<PRUint32>(rl);
   return true;
 }
+
+void nsBuiltinDecoderStateMachine::LoadMetadata()
+{
+  NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  LOG(PR_LOG_DEBUG, ("Loading Media Headers"));
+
+  nsMediaStream* stream = mDecoder->mStream;
+
+  nsVideoInfo info;
+  {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    mReader->ReadMetadata(info);
+  }
+  mInfo = info;
+  mDecoder->StartProgressUpdates();
+
+  if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
+    mState = DECODER_STATE_SHUTDOWN;      
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DecodeError);
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    return;
+  }
+
+  if (!mInfo.mHasVideo) {
+    mInfo.mCallbackPeriod = 1000 / AUDIO_FRAME_RATE;
+  }
+  LOG(PR_LOG_DEBUG, ("%p Callback Period: %u", mDecoder, mInfo.mCallbackPeriod));
+
+  // TODO: Get the duration from Skeleton index, if available.
+
+  // 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.
+  mGotDurationFromHeader = (GetDuration() != -1);
+  if (mState != DECODER_STATE_SHUTDOWN &&
+      stream->GetLength() >= 0 &&
+      mSeekable &&
+      mEndTime == -1)
+  {
+    mDecoder->StopProgressUpdates();
+    FindEndTime();
+    mDecoder->StartProgressUpdates();
+    mDecoder->UpdatePlaybackRate();
+  }
+}
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -32,17 +32,17 @@
  * 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 an Ogg file has two additional threads beyond
+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 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
@@ -61,83 +61,16 @@ the state machine thread. When playback 
 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.
 
-The nsOggPlayStateMachine class is the event that gets dispatched to
-the state machine thread. It has the following states:
-
-DECODING_METADATA
-  The Ogg 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 and audio threads are started and video frames displayed at
-  the required time. 
-SEEKING
-  A seek operation is in progress.
-BUFFERING
-  Decoding is paused while data is buffered for smooth playback.
-COMPLETED
-  The resource has completed decoding, but not finished playback. 
-SHUTDOWN
-  The decoder object is about to be destroyed.
-
-The following result in state transitions.
-
-Shutdown()
-  Clean up any resources the nsOggPlayStateMachine owns.
-Decode()
-  Start decoding video frames.
-Buffer
-  This is not user initiated. It occurs when the
-  available data in the stream drops below a certain point.
-Complete
-  This is not user initiated. It occurs when the
-  stream is completely decoded.
-Seek(float)
-  Seek to the time position given in the resource.
-
-A state transition diagram:
-
-DECODING_METADATA
-  |      |
-  v      | Shutdown()
-  |      |
-  v      -->-------------------->--------------------------|
-  |---------------->----->------------------------|        v
-DECODING             |          |  |              |        |
-  ^                  v Seek(t)  |  |              |        |
-  |         Decode() |          v  |              |        |
-  ^-----------<----SEEKING      |  v Complete     v        v
-  |                  |          |  |              |        |
-  |                  |          |  COMPLETED    SHUTDOWN-<-|
-  ^                  ^          |  |Shutdown()    |
-  |                  |          |  >-------->-----^
-  |         Decode() |Seek(t)   |Buffer()         |
-  -----------<--------<-------BUFFERING           |
-                                |                 ^
-                                v Shutdown()      |
-                                |                 |
-                                ------------>-----|
-
-The following represents the states that the nsBuiltinDecoder object
-can be in, and the valid states the decode thread can be in at that
-time:
-
-player LOADING   decoder DECODING_METADATA
-player PLAYING   decoder DECODING, BUFFERING, SEEKING, COMPLETED
-player PAUSED    decoder DECODING, BUFFERING, SEEKING, COMPLETED
-player SEEKING   decoder SEEKING
-player COMPLETED decoder SHUTDOWN
-player SHUTDOWN  decoder SHUTDOWN
-
 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 thread will skip all frames in the video queue whose
@@ -171,78 +104,80 @@ 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 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(nsOggPlayStateMachine_h__)
-#define nsOggPlayStateMachine_h__
+#if !defined(nsBuiltinDecoderStateMachine_h__)
+#define nsBuiltinDecoderStateMachine_h__
 
 #include "prmem.h"
 #include "nsThreadUtils.h"
-#include "nsOggReader.h"
 #include "nsBuiltinDecoder.h"
+#include "nsBuiltinDecoderReader.h"
 #include "nsHTMLMediaElement.h"
 #include "mozilla/Monitor.h"
 
-using mozilla::TimeDuration;
-using mozilla::TimeStamp;
-
 /*
   The playback state machine class. This manages the decoding in the
-  nsOggReader on the decode thread, seeking and in-sync-playback on 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. 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)
-    Ogg Metadata Loaded
+    Metadata Loaded
     First Frame Loaded
     Frame decoded
     data pushed or popped from the video and audio queues
 
-  See nsOggDecoder.h for more details.
+  See nsBuiltinDecoder.h for more details.
 */
-class nsOggPlayStateMachine : public nsDecoderStateMachine
+class nsBuiltinDecoderStateMachine : public nsDecoderStateMachine
 {
 public:
-  // Enumeration for the valid states
-  enum State {
-    DECODER_STATE_DECODING_METADATA,
-    DECODER_STATE_DECODING,
-    DECODER_STATE_SEEKING,
-    DECODER_STATE_BUFFERING,
-    DECODER_STATE_COMPLETED,
-    DECODER_STATE_SHUTDOWN
-  };
+  typedef mozilla::Monitor Monitor;
+  typedef mozilla::TimeStamp TimeStamp;
+  typedef mozilla::TimeDuration TimeDuration;
 
-  nsOggPlayStateMachine(nsBuiltinDecoder* aDecoder);
-  ~nsOggPlayStateMachine();
+  nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder, nsBuiltinDecoderReader* aReader);
+  ~nsBuiltinDecoderStateMachine();
 
   // nsDecoderStateMachine interface
   virtual nsresult Init();
+  State GetState()
+  { 
+    mDecoder->GetMonitor().AssertCurrentThreadIn();
+    return mState; 
+  }
   virtual void SetVolume(float aVolume);
   virtual void Shutdown();
   virtual PRInt64 GetDuration();
   virtual void SetDuration(PRInt64 aDuration);
   virtual PRBool OnDecodeThread() {
     return IsCurrentThread(mDecodeThread);
   }
 
   virtual nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
   virtual void Decode();
   virtual void Seek(float aTime);
   virtual float GetCurrentTime();
   virtual void ClearPositionChangeFlag();
   virtual void SetSeekable(PRBool aSeekable);
+  virtual void UpdatePlaybackPosition(PRInt64 aTime);
+
+
+  // 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 {
@@ -264,24 +199,24 @@ public:
             (!HasAudio() || audioQueueSize > 0)) ||
            audioQueueSize > 0;
   }
 
   // Must be called with the decode monitor held.
   PRBool IsBuffering() const {
     mDecoder->GetMonitor().AssertCurrentThreadIn();
 
-    return mState == nsOggPlayStateMachine::DECODER_STATE_BUFFERING;
+    return mState == nsBuiltinDecoderStateMachine::DECODER_STATE_BUFFERING;
   }
 
   // Must be called with the decode monitor held.
   PRBool IsSeeking() const {
     mDecoder->GetMonitor().AssertCurrentThreadIn();
 
-    return mState == nsOggPlayStateMachine::DECODER_STATE_SEEKING;
+    return mState == nsBuiltinDecoderStateMachine::DECODER_STATE_SEEKING;
   }
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   PRBool OnAudioThread() {
     return IsCurrentThread(mAudioThread);
   }
 
@@ -292,30 +227,23 @@ public:
   // 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;
 
-  // 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.
-  void UpdatePlaybackPosition(PRInt64 aTime);
-
   // 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.
   // Accessed on state machine, audio, main, and AV thread. 
   State mState;
 
-private:
+protected:
 
   // Waits on the decoder Monitor for aMs. 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 an the decoder monitor
   // don't cause the audio thread to be starved. The decoder monitor must
   // be locked.
   void Wait(PRUint32 aMs);
@@ -332,17 +260,17 @@ private:
   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 page in the Ogg file, storing the value
+  // 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();
 
   // Performs YCbCr to RGB conversion, and pushes the image down the
   // rendering pipeline. Called on the state machine thread.
   void RenderVideoFrame(VideoData* aData);
 
@@ -356,21 +284,16 @@ private:
   // Stops the decode threads. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   void StopDecodeThreads();
 
   // Starts the decode threads. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   nsresult StartDecodeThreads();
 
-  // Reads the Ogg headers using the nsOggReader, and initializes playback.
-  // Called on the state machine thread. The decoder monitor must be held with
-  // exactly one lock count.
-  void LoadOggHeaders();
-
   // 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();
 
   // Stop or pause playback of media. This has two modes, denoted by
   // aMode being either AUDIO_PAUSE or AUDIO_SHUTDOWN.
   //
@@ -389,30 +312,22 @@ private:
   // 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();
 
   // Returns PR_TRUE if we're currently playing. The decoder monitor must
   // be held.
   PRBool IsPlaying();
 
-  // Stores presentation info about required for playback of the media.
-  nsOggInfo mInfo;
-
   // Monitor 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).
   Monitor mAudioMonitor;
 
-  // 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<nsOggReader> mReader;
-
   // 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.
@@ -456,16 +371,24 @@ private:
   // this value. Accessed on main and state machine thread.
   PRInt64 mSeekTime;
 
   // The audio stream resource. Used on the state machine, audio, and main
   // threads. You must hold the mAudioMonitor, and must NOT hold the decoder
   // monitor when using the audio stream!
   nsAutoPtr<nsAudioStream> mAudioStream;
 
+  // Stores presentation info about required for playback of the media.
+  nsVideoInfo mInfo;
+
+  // 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 milliseconds. This is referenced from
   // 0 which is the initial playback position. Set by the state machine
   // thread, and read-only from the main thread to get the current
   // time value. Synchronised via decoder monitor.
   PRInt64 mCurrentFrameTime;
 
   // The presentation time of the first audio sample that was played. We can
   // add this to the audio stream position to determine the current audio time.
--- a/content/media/ogg/Makefile.in
+++ b/content/media/ogg/Makefile.in
@@ -50,17 +50,16 @@ EXPORTS		+= \
 		nsOggDecoder.h \
 		nsOggCodecState.h \
 		$(NULL)
 
 CPPSRCS		= \
 		nsOggDecoder.cpp \
 		nsOggCodecState.cpp \
 		nsOggReader.cpp \
-		nsOggPlayStateMachine.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../base/src \
--- a/content/media/ogg/nsOggDecoder.cpp
+++ b/content/media/ogg/nsOggDecoder.cpp
@@ -32,15 +32,16 @@
  * 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 ***** */
 
-#include "nsOggPlayStateMachine.h"
+#include "nsBuiltinDecoderStateMachine.h"
+#include "nsOggReader.h"
 #include "nsOggDecoder.h"
 
 nsDecoderStateMachine* nsOggDecoder::CreateStateMachine()
 {
-  return new nsOggPlayStateMachine(this);
+  return new nsBuiltinDecoderStateMachine(this, new nsOggReader(this));
 }
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggReader.cpp
@@ -0,0 +1,1375 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+#include "nsError.h"
+#include "nsBuiltinDecoderStateMachine.h"
+#include "nsBuiltinDecoder.h"
+#include "nsOggReader.h"
+#include "VideoUtils.h"
+#include "theora/theoradec.h"
+
+using namespace mozilla;
+
+// Un-comment to enable logging of seek bisections.
+//#define SEEK_LOGGING
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gBuiltinDecoderLog;
+#define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
+#ifdef SEEK_LOGGING
+#define SEEK_LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
+#else
+#define SEEK_LOG(type, msg)
+#endif
+#else
+#define LOG(type, msg)
+#define SEEK_LOG(type, msg)
+#endif
+
+// Chunk size to read when reading Ogg files. Average Ogg page length
+// is about 4300 bytes, so we read the file in chunks larger than that.
+static const int PAGE_STEP = 8192;
+
+nsOggReader::nsOggReader(nsBuiltinDecoder* aDecoder)
+  : nsBuiltinDecoderReader(aDecoder),
+    mTheoraState(nsnull),
+    mVorbisState(nsnull),
+    mPageOffset(0),
+    mCallbackPeriod(0),
+    mTheoraGranulepos(-1),
+    mVorbisGranulepos(-1)
+{
+  MOZ_COUNT_CTOR(nsOggReader);
+}
+
+nsOggReader::~nsOggReader()
+{
+  ogg_sync_clear(&mOggState);
+  MOZ_COUNT_DTOR(nsOggReader);
+}
+
+nsresult nsOggReader::Init() {
+  PRBool init = mCodecStates.Init();
+  NS_ASSERTION(init, "Failed to initialize mCodecStates");
+  if (!init) {
+    return NS_ERROR_FAILURE;
+  }
+  int ret = ogg_sync_init(&mOggState);
+  NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
+  return NS_OK;
+}
+
+nsresult nsOggReader::ResetDecode()
+{
+  nsresult res = NS_OK;
+
+  // Clear the Theora/Vorbis granulepos capture status, so that the next
+  // decode calls recaptures the granulepos.
+  mTheoraGranulepos = -1;
+  mVorbisGranulepos = -1;
+
+  if (NS_FAILED(nsBuiltinDecoderReader::ResetDecode())) {
+    res = NS_ERROR_FAILURE;
+  }
+
+  {
+    MonitorAutoEnter mon(mMonitor);
+
+    // 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;
+}
+
+// Returns PR_TRUE when all bitstreams in aBitstreams array have finished
+// reading their headers.
+static PRBool DoneReadingHeaders(nsTArray<nsOggCodecState*>& aBitstreams) {
+  for (PRUint32 i = 0; i < aBitstreams .Length(); i++) {
+    if (!aBitstreams [i]->DoneReadingHeaders()) {
+      return PR_FALSE;
+    }
+  }
+  return PR_TRUE;
+}
+
+
+nsresult nsOggReader::ReadMetadata(nsVideoInfo& aInfo)
+{
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on play state machine thread.");
+  MonitorAutoEnter mon(mMonitor);
+
+  // 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;
+  PRInt64 pageOffset;
+  nsAutoTArray<nsOggCodecState*,4> bitstreams;
+  PRBool readAllBOS = PR_FALSE;
+  mDataOffset = 0;
+  while (PR_TRUE) {
+    if (readAllBOS && DoneReadingHeaders(bitstreams)) {
+      if (mDataOffset == 0) {
+        // We've previously found the start of the first non-header packet.
+        mDataOffset = mPageOffset;
+      }
+      break;
+    }
+    pageOffset = ReadOggPage(&page);
+    if (pageOffset == -1) {
+      // Some kind of error...
+      break;
+    }
+
+    int ret = 0;
+    int serial = ogg_page_serialno(&page);
+    nsOggCodecState* codecState = 0;
+
+    if (ogg_page_bos(&page)) {
+      NS_ASSERTION(!readAllBOS, "We shouldn't encounter another BOS page");
+      codecState = nsOggCodecState::Create(&page);
+      PRBool r = mCodecStates.Put(serial, codecState);
+      NS_ASSERTION(r, "Failed to insert into mCodecStates");
+      bitstreams.AppendElement(codecState);
+      if (codecState &&
+          codecState->GetType() == nsOggCodecState::TYPE_VORBIS &&
+          !mVorbisState)
+      {
+        // First Vorbis bitstream, we'll play this one. Subsequent Vorbis
+        // bitstreams will be ignored.
+        mVorbisState = static_cast<nsVorbisState*>(codecState);
+      }
+      if (codecState &&
+          codecState->GetType() == nsOggCodecState::TYPE_THEORA &&
+          !mTheoraState)
+      {
+        // First Theora bitstream, we'll play this one. Subsequent Theora
+        // bitstreams will be ignored.
+        mTheoraState = static_cast<nsTheoraState*>(codecState);
+      }
+    } else {
+      // We've encountered the a non Beginning Of Stream page. No more
+      // BOS pages can follow in this Ogg segment, so there will be no other
+      // bitstreams in the Ogg (unless it's invalid).
+      readAllBOS = PR_TRUE;
+    }
+
+    mCodecStates.Get(serial, &codecState);
+    NS_ENSURE_TRUE(codecState, NS_ERROR_FAILURE);
+
+    // Add a complete page to the bitstream
+    ret = ogg_stream_pagein(&codecState->mState, &page);
+    NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
+
+    // Process all available header packets in the stream.
+    ogg_packet packet;
+    if (codecState->DoneReadingHeaders() && mDataOffset == 0)
+    {
+      // Stream has read all header packets, but now there's more data in
+      // (presumably) a non-header page, we must have finished header packets.
+      // This can happen in incorrectly chopped streams.
+      mDataOffset = pageOffset;
+      continue;
+    }
+    while (!codecState->DoneReadingHeaders() &&
+           (ret = ogg_stream_packetout(&codecState->mState, &packet)) != 0)
+    {
+      if (ret == -1) {
+        // Sync lost, we've probably encountered the continuation of a packet
+        // in a chopped video.
+        continue;
+      }
+      // A packet is available. If it is not a header packet we'll break.
+      // If it is a header packet, process it as normal.
+      codecState->DecodeHeader(&packet);
+    }
+    if (ogg_stream_packetpeek(&codecState->mState, &packet) != 0 &&
+        mDataOffset == 0)
+    {
+      // We're finished reading headers for this bitstream, but there's still
+      // packets in the bitstream to read. The bitstream is probably poorly
+      // muxed, and includes the last header packet on a page with non-header
+      // packets. We need to ensure that this is the media start page offset.
+      mDataOffset = pageOffset;
+    }
+  }
+  // Deactivate any non-primary bitstreams.
+  for (PRUint32 i = 0; i < bitstreams.Length(); i++) {
+    nsOggCodecState* s = bitstreams[i];
+    if (s != mVorbisState && s != mTheoraState) {
+      s->Deactivate();
+    }
+  }
+
+  // Initialize the first Theora and Vorbis bitstreams. According to the
+  // Theora spec these can be considered the 'primary' bitstreams for playback.
+  // Extract the metadata needed from these streams.
+  float aspectRatio = 0;
+  if (mTheoraState) {
+    if (mTheoraState->Init()) {
+      mCallbackPeriod = mTheoraState->mFrameDuration;
+      aspectRatio = mTheoraState->mAspectRatio;
+      gfxIntSize sz(mTheoraState->mInfo.pic_width,
+                    mTheoraState->mInfo.pic_height);
+      mDecoder->SetVideoData(sz, mTheoraState->mAspectRatio, nsnull);
+    } else {
+      mTheoraState = nsnull;
+    }
+  }
+  if (mVorbisState) {
+    mVorbisState->Init();
+  }
+
+  aInfo.mHasAudio = HasAudio();
+  aInfo.mHasVideo = HasVideo();
+  aInfo.mCallbackPeriod = mCallbackPeriod;
+  if (HasAudio()) {
+    aInfo.mAudioRate = mVorbisState->mInfo.rate;
+    aInfo.mAudioChannels = mVorbisState->mInfo.channels;
+  }
+  if (HasVideo()) {
+    aInfo.mFramerate = mTheoraState->mFrameRate;
+    aInfo.mAspectRatio = mTheoraState->mAspectRatio;
+    aInfo.mPicture.width = mTheoraState->mInfo.pic_width;
+    aInfo.mPicture.height = mTheoraState->mInfo.pic_height;
+    aInfo.mPicture.x = mTheoraState->mInfo.pic_x;
+    aInfo.mPicture.y = mTheoraState->mInfo.pic_y;
+    aInfo.mFrame.width = mTheoraState->mInfo.frame_width;
+    aInfo.mFrame.height = mTheoraState->mInfo.frame_height;
+  }
+  aInfo.mDataOffset = mDataOffset;
+
+  LOG(PR_LOG_DEBUG, ("Done loading headers, data offset %lld", mDataOffset));
+
+  return NS_OK;
+}
+
+nsresult nsOggReader::DecodeVorbis(nsTArray<SoundData*>& aChunks,
+                                   ogg_packet* aPacket)
+{
+  // Successfully read a packet.
+  if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
+    return NS_ERROR_FAILURE;
+  }
+  if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
+                               &mVorbisState->mBlock) != 0)
+  {
+    return NS_ERROR_FAILURE;
+  }
+
+  float** pcm = 0;
+  PRUint32 samples = 0;
+  PRUint32 channels = mVorbisState->mInfo.channels;
+  while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
+    if (samples > 0) {
+      float* buffer = new float[samples * channels];
+      float* p = buffer;
+      for (PRUint32 i = 0; i < samples; ++i) {
+        for (PRUint32 j = 0; j < channels; ++j) {
+          *p++ = pcm[j][i];
+        }
+      }
+
+      PRInt64 duration = mVorbisState->Time((PRInt64)samples);
+      PRInt64 startTime = (mVorbisGranulepos != -1) ?
+        mVorbisState->Time(mVorbisGranulepos) : -1;
+      SoundData* s = new SoundData(mPageOffset,
+                                   startTime,
+                                   duration,
+                                   samples,
+                                   buffer,
+                                   channels);
+      if (mVorbisGranulepos != -1) {
+        mVorbisGranulepos += samples;
+      }
+      aChunks.AppendElement(s);
+    }
+    if (vorbis_synthesis_read(&mVorbisState->mDsp, samples) != 0) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return NS_OK;
+}
+
+PRBool nsOggReader::DecodeAudioData()
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on playback or decode thread.");
+  NS_ASSERTION(mVorbisState!=0, "Need Vorbis state to decode audio");
+  ogg_packet packet;
+  packet.granulepos = -1;
+
+  PRBool endOfStream = PR_FALSE;
+
+  nsAutoTArray<SoundData*, 64> chunks;
+  if (mVorbisGranulepos == -1) {
+    // Not captured Vorbis granulepos, read up until we get a granulepos, and
+    // back propagate the granulepos.
+
+    // We buffer the packets' pcm samples until we reach a packet with a granulepos.
+    // This will be the last packet in a page. Then using that granulepos to 
+    // calculate the packet's end time, we calculate all the packets' start times by
+    // subtracting their durations.
+
+    // Ensure we've got Vorbis packets; read one more Vorbis page if necessary.
+    while (packet.granulepos <= 0 && !endOfStream) {
+      if (!ReadOggPacket(mVorbisState, &packet)) {
+        endOfStream = PR_TRUE;
+        break;
+      }
+      if (packet.e_o_s != 0) {
+        // This packet marks the logical end of the Vorbis bitstream. It may
+        // still contain sound samples, so we must still decode it.
+        endOfStream = PR_TRUE;
+      }
+
+      if (NS_FAILED(DecodeVorbis(chunks, &packet))) {
+        NS_WARNING("Failed to decode Vorbis packet");
+      }
+    }
+
+    if (packet.granulepos > 0) {
+      // Successfully read up to a non -1 granulepos.
+      // Calculate the timestamps of the sound samples.
+      PRInt64 granulepos = packet.granulepos; // Represents end time of last sample.
+      mVorbisGranulepos = packet.granulepos;
+      for (int i = chunks.Length() - 1; i >= 0; --i) {
+        SoundData* s = chunks[i];
+        PRInt64 startGranule = granulepos - s->mSamples;
+        s->mTime = mVorbisState->Time(startGranule);
+        granulepos = startGranule;
+      }
+    }
+  } else {
+    // We have already captured the granulepos. The next packet's granulepos
+    // is its number of samples, plus the previous granulepos.
+    if (!ReadOggPacket(mVorbisState, &packet)) {
+      endOfStream = PR_TRUE;
+    } else {
+      // Successfully read a packet from the file. Decode it.
+      endOfStream = packet.e_o_s != 0;
+
+      // Try to decode any packet we've read.
+      if (NS_FAILED(DecodeVorbis(chunks, &packet))) {
+        NS_WARNING("Failed to decode Vorbis packet");
+      }
+
+      if (packet.granulepos != -1 && packet.granulepos != mVorbisGranulepos) {
+        // If the packet's granulepos doesn't match our running sample total,
+        // it's likely the bitstream has been damaged somehow, or perhaps
+        // oggz-chopped. Just assume the packet's granulepos is correct...
+        mVorbisGranulepos = packet.granulepos;
+      }
+    }
+  }
+
+  // We've successfully decoded some sound chunks. Push them onto the audio
+  // queue.
+  for (PRUint32 i = 0; i < chunks.Length(); ++i) {
+    mAudioQueue.Push(chunks[i]);
+  }
+
+  if (endOfStream) {
+    // We've encountered an end of bitstream packet, or we've hit the end of
+    // file while trying to decode, so inform the audio queue that there'll
+    // be no more samples.
+    mAudioQueue.Finish();
+    return PR_FALSE;
+  }
+
+  return PR_TRUE;
+}
+
+// Returns 1 if the Theora info struct is decoding a media of Theora
+// verion (maj,min,sub) or later, otherwise returns 0.
+static int
+TheoraVersion(th_info* info,
+              unsigned char maj,
+              unsigned char min,
+              unsigned char sub)
+{
+  ogg_uint32_t ver = (maj << 16) + (min << 8) + sub;
+  ogg_uint32_t th_ver = (info->version_major << 16) +
+                        (info->version_minor << 8) +
+                        info->version_subminor;
+  return (th_ver >= ver) ? 1 : 0;
+}
+
+#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<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
+
+static void Clear(nsTArray<VideoData*>& aFrames) {
+  for (PRUint32 i = 0; i < aFrames.Length(); ++i) {
+    delete aFrames[i];
+  }
+  aFrames.Clear();
+}
+
+nsresult nsOggReader::DecodeTheora(nsTArray<VideoData*>& aFrames,
+                                   ogg_packet* aPacket)
+{
+  int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
+  if (ret != 0 && ret != TH_DUPFRAME) {
+    return NS_ERROR_FAILURE;
+  }
+  PRInt64 time = (aPacket->granulepos != -1)
+    ? mTheoraState->StartTime(aPacket->granulepos) : -1;
+  if (ret == TH_DUPFRAME) {
+    aFrames.AppendElement(VideoData::CreateDuplicate(mPageOffset,
+                                                     time,
+                                                     aPacket->granulepos));
+  } else if (ret == 0) {
+    th_ycbcr_buffer buffer;
+    ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
+    NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
+    PRBool isKeyframe = th_packet_iskeyframe(aPacket) == 1;
+    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;
+    }
+    VideoData *v = VideoData::Create(mPageOffset,
+                                     time,
+                                     b,
+                                     isKeyframe,
+                                     aPacket->granulepos);
+    if (!v) {
+      NS_WARNING("Failed to allocate memory for video frame");
+      Clear(aFrames);
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    aFrames.AppendElement(v);
+  }
+  return NS_OK;
+}
+
+PRBool nsOggReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
+                                     PRInt64 aTimeThreshold)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine or AV thread.");
+  // We chose to keep track of the Theora granulepos ourselves, rather than
+  // rely on th_decode_packetin() to do it for us. This is because
+  // th_decode_packetin() simply works by incrementing a counter every time
+  // it's called, so if we drop frames and don't call it, subsequent granulepos
+  // will be wrong. Whenever we read a packet which has a granulepos, we use
+  // its granulepos, otherwise we increment the previous packet's granulepos.
+
+  nsAutoTArray<VideoData*, 8> frames;
+  ogg_packet packet;
+  PRBool endOfStream = PR_FALSE;
+  if (mTheoraGranulepos == -1) {
+    // We've not read a Theora packet with a granulepos, so we don't know what
+    // timestamp to assign to Theora frames we decode. This will only happen
+    // the first time we read, or after a seek. We must read and buffer up to
+    // the first Theora packet with a granulepos, and back-propagate its 
+    // granulepos to calculate the buffered frames' granulepos.
+    do {
+      if (!ReadOggPacket(mTheoraState, &packet)) {
+        // Failed to read another page, must be the end of file. We can't have
+        // already encountered an end of bitstream packet, else we wouldn't be
+        // here, so this bitstream must be missing its end of stream packet, or
+        // is otherwise corrupt (oggz-chop can output files like this). Inform
+        // the queue that there will be no more frames.
+        mVideoQueue.Finish();
+        return PR_FALSE;
+      }
+
+      if (packet.granulepos > 0) {
+        // We've found a packet with a granulepos, we can now determine the
+        // buffered packet's timestamps, as well as the timestamps for any
+        // packets we read subsequently.
+        mTheoraGranulepos = packet.granulepos;
+      }
+    
+      if (DecodeTheora(frames, &packet) == NS_ERROR_OUT_OF_MEMORY) {
+        NS_WARNING("Theora decode memory allocation failure!");
+        return PR_FALSE;
+      }
+
+    } while (packet.granulepos <= 0 && !endOfStream);
+
+    if (packet.granulepos > 0) {
+      // We have captured a granulepos. Backpropagate the granulepos
+      // to determine buffered packets' timestamps.
+      PRInt64 succGranulepos = packet.granulepos;
+      int version_3_2_1 = TheoraVersion(&mTheoraState->mInfo,3,2,1);
+      int shift = mTheoraState->mInfo.keyframe_granule_shift;
+      for (int i = frames.Length() - 2; i >= 0; --i) {
+        PRInt64 granulepos = succGranulepos;
+        if (frames[i]->mKeyframe) {
+          // This frame is a keyframe. It's granulepos is the previous granule
+          // number minus 1, shifted by granuleshift.
+          ogg_int64_t frame_index = th_granule_frame(mTheoraState->mCtx,
+                                                     granulepos);
+          granulepos = (frame_index + version_3_2_1 - 1) << shift;
+          // Theora 3.2.1+ granulepos store frame number [1..N], so granulepos
+          // should be > 0.
+          // Theora 3.2.0 granulepos store the frame index [0..(N-1)], so
+          // granulepos should be >= 0. 
+          NS_ASSERTION((version_3_2_1 && granulepos > 0) ||
+                       granulepos >= 0, "Should have positive granulepos");
+        } else {
+          // Packet is not a keyframe. It's granulepos depends on its successor
+          // packet...
+          if (frames[i+1]->mKeyframe) {
+            // The successor frame is a keyframe, so we can't just subtract 1
+            // from the "keyframe offset" part of its granulepos, as it
+            // doesn't have one! So fake it, take the keyframe offset as the
+            // max possible keyframe offset. This means the granulepos (probably)
+            // overshoots and claims that it depends on a frame before its actual
+            // keyframe but at least its granule number will be correct, so the
+            // times we calculate from this granulepos will also be correct.
+            ogg_int64_t frameno = th_granule_frame(mTheoraState->mCtx,
+                                                   granulepos);
+            ogg_int64_t max_offset = NS_MIN((frameno - 1),
+                                         (ogg_int64_t)(1 << shift) - 1);
+            ogg_int64_t granule = frameno +
+                                  TheoraVersion(&mTheoraState->mInfo,3,2,1) -
+                                  1 - max_offset;
+            NS_ASSERTION(granule > 0, "Must have positive granulepos");
+            granulepos = (granule << shift) + max_offset;
+          } else {
+            // Neither previous nor this frame are keyframes, so we can just
+            // decrement the previous granulepos to calculate this frames
+            // granulepos.
+            --granulepos;
+          }
+        }
+        // Check that the frame's granule number (it's frame number) is
+        // one less than the successor frame.
+        NS_ASSERTION(th_granule_frame(mTheoraState->mCtx, succGranulepos) ==
+                     th_granule_frame(mTheoraState->mCtx, granulepos) + 1,
+                     "Granulepos calculation is incorrect!");
+        frames[i]->mTime = mTheoraState->StartTime(granulepos);
+        frames[i]->mTimecode = granulepos;
+        succGranulepos = granulepos;
+        NS_ASSERTION(frames[i]->mTime < frames[i+1]->mTime, "Times should increase");      
+      }
+      NS_ASSERTION(AllFrameTimesIncrease(frames), "All frames must have granulepos");
+    }
+  } else {
+    
+    NS_ASSERTION(mTheoraGranulepos > 0, "We must Theora granulepos!");
+    
+    if (!ReadOggPacket(mTheoraState, &packet)) {
+      // Failed to read from file, so EOF or other premature failure.
+      // Inform the queue that there will be no more frames.
+      mVideoQueue.Finish();
+      return PR_FALSE;
+    }
+
+    endOfStream = packet.e_o_s != 0;
+
+    // Maintain the Theora granulepos. We must do this even if we drop frames,
+    // otherwise our clock will be wrong after we've skipped frames.
+    if (packet.granulepos != -1) {
+      // Incoming packet has a granulepos, use that as it's granulepos.
+      mTheoraGranulepos = packet.granulepos;
+    } else {
+      // Increment the previous Theora granulepos.
+      PRInt64 granulepos = 0;
+      int shift = mTheoraState->mInfo.keyframe_granule_shift;
+      // Theora 3.2.1+ bitstreams granulepos store frame number; [1..N]
+      // Theora 3.2.0 bitstreams store the frame index; [0..(N-1)]
+      if (!th_packet_iskeyframe(&packet)) {
+        granulepos = mTheoraGranulepos + 1;
+      } else {
+        ogg_int64_t frameindex = th_granule_frame(mTheoraState->mCtx,
+                                                  mTheoraGranulepos);
+        ogg_int64_t granule = frameindex +
+                              TheoraVersion(&mTheoraState->mInfo,3,2,1) + 1;
+        NS_ASSERTION(granule > 0, "Must have positive granulepos");
+        granulepos = granule << shift;
+      }
+
+      NS_ASSERTION(th_granule_frame(mTheoraState->mCtx, mTheoraGranulepos) + 1 == 
+                   th_granule_frame(mTheoraState->mCtx, granulepos),
+                   "Frame number must increment by 1");
+      packet.granulepos = mTheoraGranulepos = granulepos;
+    }
+
+    PRInt64 time = mTheoraState->StartTime(mTheoraGranulepos);
+    NS_ASSERTION(packet.granulepos != -1, "Must know packet granulepos");
+    if (!aKeyframeSkip ||
+        (th_packet_iskeyframe(&packet) == 1 && time >= aTimeThreshold))
+    {
+      if (DecodeTheora(frames, &packet) == NS_ERROR_OUT_OF_MEMORY) {
+        NS_WARNING("Theora decode memory allocation failure");
+        return PR_FALSE;
+      }
+    }
+  }
+
+  // Push decoded data into the video frame queue.
+  for (PRUint32 i = 0; i < frames.Length(); i++) {
+    nsAutoPtr<VideoData> data(frames[i]);
+    if (!aKeyframeSkip || (aKeyframeSkip && frames[i]->mKeyframe)) {
+      mVideoQueue.Push(data.forget());
+      if (aKeyframeSkip && frames[i]->mKeyframe) {
+        aKeyframeSkip = PR_FALSE;
+      }
+    } else {
+      frames[i] = nsnull;
+      data = nsnull;
+    }
+  }
+
+  if (endOfStream) {
+    // We've encountered an end of bitstream packet. Inform the queue that
+    // there will be no more frames.
+    mVideoQueue.Finish();
+  }
+
+  return !endOfStream;
+}
+
+PRInt64 nsOggReader::ReadOggPage(ogg_page* aPage)
+{
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on play state machine or decode thread.");
+  mMonitor.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;
+    }
+    // Returns a buffer that can be written too
+    // with the given size. This buffer is stored
+    // in the ogg synchronisation structure.
+    char* buffer = ogg_sync_buffer(&mOggState, 4096);
+    NS_ASSERTION(buffer, "ogg_sync_buffer failed");
+
+    // Read from the stream into the buffer
+    PRUint32 bytesRead = 0;
+
+    nsresult rv = mDecoder->GetCurrentStream()->Read(buffer, 4096, &bytesRead);
+    if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
+      // End of file.
+      return -1;
+    }
+
+    mDecoder->NotifyBytesConsumed(bytesRead);
+    // Update the synchronisation layer with the number
+    // of bytes written to the buffer
+    ret = ogg_sync_wrote(&mOggState, bytesRead);
+    NS_ENSURE_TRUE(ret == 0, -1);    
+  }
+  PRInt64 offset = mPageOffset;
+  mPageOffset += aPage->header_len + aPage->body_len;
+  
+  return offset;
+}
+
+PRBool nsOggReader::ReadOggPacket(nsOggCodecState* aCodecState,
+                                  ogg_packet* aPacket)
+{
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on play state machine or decode thread.");
+  mMonitor.AssertCurrentThreadIn();
+
+  if (!aCodecState || !aCodecState->mActive) {
+    return PR_FALSE;
+  }
+
+  int ret = 0;
+  while ((ret = ogg_stream_packetout(&aCodecState->mState, aPacket)) != 1) {
+    ogg_page page;
+
+    if (aCodecState->PageInFromBuffer()) {
+      // The codec state has inserted a previously buffered page into its
+      // ogg_stream_state, no need to read a page from the channel.
+      continue;
+    }
+
+    // The codec state does not have any buffered pages, so try to read another
+    // page from the channel.
+    if (ReadOggPage(&page) == -1) {
+      return PR_FALSE;
+    }
+
+    PRUint32 serial = ogg_page_serialno(&page);
+    nsOggCodecState* codecState = nsnull;
+    mCodecStates.Get(serial, &codecState);
+
+    if (serial == aCodecState->mSerial) {
+      // This page is from our target bitstream, insert it into the
+      // codec state's ogg_stream_state so we can read a packet.
+      ret = ogg_stream_pagein(&codecState->mState, &page);
+      NS_ENSURE_TRUE(ret == 0, PR_FALSE);
+    } else if (codecState && codecState->mActive) {
+      // Page is for another active bitstream, add the page to its codec
+      // state's buffer for later consumption when that stream next tries
+      // to read a packet.
+      codecState->AddToBuffer(&page);
+    }
+  }
+
+  return PR_TRUE;
+}
+
+// Returns an ogg page's checksum.
+static ogg_uint32_t
+GetChecksum(ogg_page* page)
+{
+  if (page == 0 || page->header == 0 || page->header_len < 25) {
+    return 0;
+  }
+  const unsigned char* p = page->header + 22;
+  PRUint32 c =  p[0] +
+               (p[1] << 8) + 
+               (p[2] << 16) +
+               (p[3] << 24);
+  return c;
+}
+
+PRInt64 nsOggReader::FindEndTime(PRInt64 aEndOffset)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
+
+  nsMediaStream* stream = mDecoder->GetCurrentStream();
+  ogg_sync_reset(&mOggState);
+
+  stream->Seek(nsISeekableStream::NS_SEEK_SET, aEndOffset);
+
+  // 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 offset = aEndOffset;
+  PRInt64 endTime = -1;
+  PRUint32 checksumAfterSeek = 0;
+  PRUint32 prevChecksumAfterSeek = 0;
+  PRBool mustBackOff = PR_FALSE;
+  while (PR_TRUE) {
+    {
+      MonitorAutoExit exitReaderMon(mMonitor);
+      MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+      if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+        return -1;
+      }
+    }
+    ogg_page page;    
+    int ret = ogg_sync_pageseek(&mOggState, &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 || stream->Tell() == aEndOffset) {
+        if (endTime != -1) {
+          // 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(&mOggState);
+        offset = NS_MAX(static_cast<PRInt64>(0), offset - step);
+        stream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+      }
+      NS_ASSERTION(stream->Tell() < aEndOffset,
+                   "Stream pos must be before range end");
+
+      PRInt64 limit = NS_MIN(static_cast<PRInt64>(PR_UINT32_MAX),
+                             aEndOffset - stream->Tell());
+      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(&mOggState,
+                                     bytesToRead);
+      NS_ASSERTION(buffer, "Must have buffer");
+      stream->Read(buffer, bytesToRead, &bytesRead);
+
+      // Update the synchronisation layer with the number
+      // of bytes written to the buffer
+      ret = ogg_sync_wrote(&mOggState, bytesRead);
+      if (ret != 0) {
+        endTime = -1;
+        break;
+      }
+
+      continue;
+    }
+
+    if (ret < 0 || ogg_page_granulepos(&page) < 0) {
+      continue;
+    }
+
+    PRUint32 checksum = GetChecksum(&page);
+    if (checksumAfterSeek == 0) {
+      // This is the first page we've decoded after a backoff/seek. Remember
+      // the page checksum. If we backoff further and encounter this page
+      // again, we'll know that we won't find a page with an end time after
+      // this one, so we'll know to back off again.
+      checksumAfterSeek = checksum;
+    }
+    if (checksum == prevChecksumAfterSeek) {
+      // This page has the same checksum as the first page we encountered
+      // after the last backoff/seek. Since we've already scanned after this
+      // page and failed to find an end time, we may as well backoff again and
+      // try to find an end time from an earlier page.
+      mustBackOff = PR_TRUE;
+      continue;
+    }
+
+    PRInt64 granulepos = ogg_page_granulepos(&page);
+    int serial = ogg_page_serialno(&page);
+
+    nsOggCodecState* codecState = nsnull;
+    mCodecStates.Get(serial, &codecState);
+
+    if (!codecState) {
+      // This page is from a bitstream which we haven't encountered yet.
+      // It's probably from a new "link" in a "chained" ogg. Don't
+      // bother even trying to find a duration...
+      break;
+    }
+
+    PRInt64 t = codecState ? codecState->Time(granulepos) : -1;
+    if (t != -1) {
+      endTime = t;
+    }
+  }
+
+  ogg_sync_reset(&mOggState);
+
+  return endTime;
+}
+
+nsresult nsOggReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
+  LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
+  nsMediaStream* stream = mDecoder->GetCurrentStream();
+
+  if (NS_FAILED(ResetDecode())) {
+    return NS_ERROR_FAILURE;
+  }
+  if (aTarget == aStartTime) {
+    stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
+    mPageOffset = mDataOffset;
+    NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
+    {
+      MonitorAutoExit exitReaderMon(mMonitor);
+      MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+      mDecoder->UpdatePlaybackPosition(aStartTime);
+    }
+  } else {
+
+    // Determine the already downloaded data in the media cache. 
+    nsAutoTArray<ByteRange, 16> ranges;
+    stream->Pin();
+    if (NS_FAILED(GetBufferedBytes(ranges))) {
+      stream->Unpin();
+      return NS_ERROR_FAILURE;
+    }
+
+    // Try to seek in the cached data ranges first, before falling back to
+    // seeking over the network. This makes seeking in buffered ranges almost
+    // instantaneous.
+    ByteRange r = GetSeekRange(ranges, aTarget, aStartTime, aEndTime, PR_TRUE);
+    nsresult res = NS_ERROR_FAILURE;
+    if (!r.IsNull()) {
+      // The frame should be in this buffered range. Seek exactly there.
+      res = SeekBisection(aTarget, r, 0);
+
+      if (NS_SUCCEEDED(res) && HasVideo()) {
+        // 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);
+          {
+            MonitorAutoExit exitReaderMon(mMonitor);
+            MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+            if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+              stream->Unpin();
+              return NS_ERROR_FAILURE;
+            }
+          }
+        } while (!eof &&
+                 mVideoQueue.GetSize() == 0);
+      
+        VideoData* video = mVideoQueue.PeekFront();
+        if (video && !video->mKeyframe) {
+          // First decoded frame isn't a keyframe, seek back to previous keyframe,
+          // otherwise we'll get visual artifacts.
+          NS_ASSERTION(video->mTimecode != -1, "Must have a granulepos");
+          int shift = mTheoraState->mInfo.keyframe_granule_shift;
+          PRInt64 keyframeGranulepos = (video->mTimecode >> shift) << shift;
+          PRInt64 keyframeTime = mTheoraState->StartTime(keyframeGranulepos);
+          
+          SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it",
+                                  video->mTime, keyframeTime));
+          ByteRange k = GetSeekRange(ranges,
+                                     keyframeTime,
+                                     aStartTime,
+                                     aEndTime,
+                                     PR_FALSE);
+          res = SeekBisection(keyframeTime, k, 500);
+          NS_ASSERTION(mTheoraGranulepos == -1, "SeekBisection must reset Theora decode");
+          NS_ASSERTION(mVorbisGranulepos == -1, "SeekBisection must reset Vorbis decode");
+        }
+      }
+    }
+
+    stream->Unpin();
+
+    if (NS_FAILED(res)) {
+      // We failed to find the seek target (or perhaps its keyframe, somehow?)
+      // in a buffered range. Minimize the bisection search space using the
+      // buffered ranges, and perform a bisection search.
+
+      // If we've got an active Theora bitstream, determine the maximum possible
+      // time in ms which a keyframe could be before a given interframe. We
+      // subtract this from our seek target, seek to the new target, and then
+      // decode forwards to the original seek target. We should encounter a
+      // keyframe in that interval. This prevents us from needing to run two
+      // bisections; one for the seek target frame, and another to find its
+      // keyframe. It's usually faster to just download this extra data, rather
+      // tham perform two bisections to find the seek target's keyframe. We
+      // don't do this offsetting when seeking in a buffered ranges (above),
+      // as the extra decoding causes a noticable speed hit when all the data
+      // is buffered.
+      PRInt64 keyframeOffsetMs = 0;
+      if (HasVideo() && mTheoraState) {
+        keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
+      }
+      PRInt64 seekTarget = NS_MAX(aStartTime, aTarget - keyframeOffsetMs);
+
+      ByteRange k = GetSeekRange(ranges, seekTarget, aStartTime, aEndTime, PR_FALSE);
+      res = SeekBisection(seekTarget, k, 500);
+
+      NS_ENSURE_SUCCESS(res, res);
+      NS_ASSERTION(mTheoraGranulepos == -1, "SeekBisection must reset Theora decode");
+      NS_ASSERTION(mVorbisGranulepos == -1, "SeekBisection must reset Vorbis decode");
+    }
+  }
+
+  // Decode forward to the seek target frame. Start with video, if we have it.
+  // We should pass a keyframe while doing this.
+  if (HasVideo()) {
+    nsAutoPtr<VideoData> video;
+    PRBool eof = PR_FALSE;
+    PRInt64 startTime = -1;
+    video = nsnull;
+    while (HasVideo() && !eof) {
+      while (mVideoQueue.GetSize() == 0 && !eof) {
+        PRBool skip = PR_FALSE;
+        eof = !DecodeVideoFrame(skip, 0);
+        {
+          MonitorAutoExit exitReaderMon(mMonitor);
+          MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+          if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+            return NS_ERROR_FAILURE;
+          }
+        }
+      }
+      if (mVideoQueue.GetSize() == 0) {
+        break;
+      }
+      video = mVideoQueue.PeekFront();
+      // If the frame end time is less than the seek target, we won't want
+      // to display this frame after the seek, so discard it.
+      if (video && video->mTime + mCallbackPeriod < aTarget) {
+        if (startTime == -1) {
+          startTime = video->mTime;
+        }
+        mVideoQueue.PopFront();
+        video = nsnull;
+      } else {
+        video.forget();
+        break;
+      }
+    }
+    {
+      MonitorAutoExit exitReaderMon(mMonitor);
+      MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+      if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+        return NS_ERROR_FAILURE;
+      }
+    }
+    SEEK_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime));
+  }
+
+  if (HasAudio()) {
+    // Decode audio forward to the seek target.
+    nsAutoPtr<SoundData> audio;
+    bool eof = PR_FALSE;
+    while (HasAudio() && !eof) {
+      while (!eof && mAudioQueue.GetSize() == 0) {
+        eof = !DecodeAudioData();
+        {
+          MonitorAutoExit exitReaderMon(mMonitor);
+          MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+          if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+            return NS_ERROR_FAILURE;
+          }
+        }
+      }
+      audio = mAudioQueue.PeekFront();
+      if (audio && audio->mTime + audio->mDuration <= aTarget) {
+        mAudioQueue.PopFront();
+        audio = nsnull;
+      } else {
+        audio.forget();
+        break;
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+enum PageSyncResult {
+  PAGE_SYNC_ERROR = 1,
+  PAGE_SYNC_END_OF_RANGE= 2,
+  PAGE_SYNC_OK = 3
+};
+
+// Reads a page from the media stream.
+static PageSyncResult
+PageSync(ogg_sync_state* aState,
+         nsMediaStream* aStream,
+         PRInt64 aEndOffset,
+         ogg_page* aPage,
+         int& aSkippedBytes)
+{
+  aSkippedBytes = 0;
+  // Sync to the next page.
+  int ret = 0;
+  PRUint32 bytesRead = 0;
+  while (ret <= 0) {
+    ret = ogg_sync_pageseek(aState, aPage);
+    if (ret == 0) {
+      char* buffer = ogg_sync_buffer(aState, PAGE_STEP);
+      NS_ASSERTION(buffer, "Must have a buffer");
+
+      // Read from the file into the buffer
+      PRInt64 bytesToRead = NS_MIN(static_cast<PRInt64>(PAGE_STEP),
+                                   aEndOffset - aStream->Tell());
+      if (bytesToRead <= 0) {
+        return PAGE_SYNC_END_OF_RANGE;
+      }
+      nsresult rv = aStream->Read(buffer,
+                                  static_cast<PRUint32>(bytesToRead),
+                                  &bytesRead);
+      if (NS_FAILED(rv)) {
+        return PAGE_SYNC_ERROR;
+      }
+
+      if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
+        // End of file.
+        return PAGE_SYNC_END_OF_RANGE;
+      }
+
+      // Update the synchronisation layer with the number
+      // of bytes written to the buffer
+      ret = ogg_sync_wrote(aState, bytesRead);
+      NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);    
+      continue;
+    }
+
+    if (ret < 0) {
+      NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
+      aSkippedBytes += -ret;
+      NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
+      continue;
+    }
+  }
+  
+  return PAGE_SYNC_OK;
+}
+
+nsresult nsOggReader::SeekBisection(PRInt64 aTarget,
+                                    const ByteRange& aRange,
+                                    PRUint32 aFuzz)
+{
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
+  nsMediaStream* stream = mDecoder->GetCurrentStream();
+
+  if (aTarget == aRange.mTimeStart) {
+    if (NS_FAILED(ResetDecode())) {
+      return NS_ERROR_FAILURE;
+    }
+    stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
+    mPageOffset = mDataOffset;
+    return NS_OK;
+  }
+
+  // Bisection search, find start offset of last page with end time less than
+  // the seek target.
+  ogg_int64_t startOffset = aRange.mOffsetStart;
+  ogg_int64_t startTime = aRange.mTimeStart;
+  ogg_int64_t startLength = 0;
+  ogg_int64_t endOffset = aRange.mOffsetEnd;
+  ogg_int64_t endTime = aRange.mTimeEnd;
+
+  ogg_int64_t seekTarget = aTarget;
+  PRInt64 seekLowerBound = NS_MAX(static_cast<PRInt64>(0), aTarget - aFuzz);
+  int hops = 0;
+  ogg_int64_t previousGuess = -1;
+  int backsteps = 1;
+  const int maxBackStep = 10;
+  NS_ASSERTION(static_cast<PRUint64>(PAGE_STEP) * pow(2.0, maxBackStep) < PR_INT32_MAX,
+               "Backstep calculation must not overflow");
+  while (PR_TRUE) {
+    ogg_int64_t duration = 0;
+    double target = 0;
+    ogg_int64_t interval = 0;
+    ogg_int64_t guess = 0;
+    ogg_page page;
+    int skippedBytes = 0;
+    ogg_int64_t pageOffset = 0;
+    ogg_int64_t pageLength = 0;
+    int backoff = 0;
+    ogg_int64_t granuleTime = -1;
+    PRInt64 oldPageOffset = 0;
+
+    // Guess where we should bisect to, based on the bit rate and the time
+    // remaining in the interval.
+    while (PR_TRUE) {
+  
+      // Discard any previously buffered packets/pages.
+      if (NS_FAILED(ResetDecode())) {
+        return NS_ERROR_FAILURE;
+      }
+
+      // Guess bisection point.
+      duration = endTime - startTime;
+      target = (double)(seekTarget - startTime) / (double)duration;
+      interval = endOffset - startOffset - startLength;
+      guess = startOffset + startLength +
+              (ogg_int64_t)((double)interval * target) - backoff;
+      guess = NS_MIN(guess, endOffset - PAGE_STEP);
+      guess = NS_MAX(guess, startOffset + startLength);
+
+      if (interval == 0 || guess == previousGuess) {
+        interval = 0;
+        // Our interval is empty, we've found the optimal seek point, as the
+        // start page is before the seek target, and the end page after the
+        // seek target.
+        break;
+      }
+
+      NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
+      NS_ASSERTION(guess < endOffset, "Guess must be before range end");
+      NS_ASSERTION(guess != previousGuess, "Guess should be differnt to previous");
+      previousGuess = guess;
+
+      SEEK_LOG(PR_LOG_DEBUG, ("Seek loop offset_start=%lld start_end=%lld "
+                              "offset_guess=%lld offset_end=%lld interval=%lld "
+                              "target=%lf time_start=%lld time_end=%lld",
+                              startOffset, (startOffset+startLength), guess,
+                              endOffset, interval, target, startTime, endTime));
+      hops++;
+      stream->Seek(nsISeekableStream::NS_SEEK_SET, guess);
+    
+      // We've seeked into the media somewhere. Locate the next page, and then
+      // figure out the granule time of the audio and video bitstreams there.
+      // We can then make a bisection decision based on our location in the media.
+      
+      PageSyncResult res = PageSync(&mOggState,
+                                    stream,
+                                    endOffset,
+                                    &page,
+                                    skippedBytes);
+      if (res == PAGE_SYNC_ERROR) {
+        return NS_ERROR_FAILURE;
+      }
+
+      // We've located a page of length |ret| at |guess + skippedBytes|.
+      // Remember where the page is located.
+      pageOffset = guess + skippedBytes;
+      pageLength = page.header_len + page.body_len;
+      mPageOffset = pageOffset + pageLength;
+
+      if (mPageOffset == endOffset || res == PAGE_SYNC_END_OF_RANGE) {
+        // Our guess was too close to the end, we've ended up reading the end
+        // page. Backoff exponentially from the end point, in case the last
+        // page/frame/sample is huge.
+        backsteps = NS_MIN(backsteps + 1, maxBackStep);
+        backoff = PAGE_STEP * pow(2.0, backsteps);
+        continue;
+      }
+
+      NS_ASSERTION(mPageOffset < endOffset, "Page read cursor should be inside range");
+
+      // Read pages until we can determine the granule time of the audio and 
+      // video bitstream.
+      ogg_int64_t audioTime = -1;
+      ogg_int64_t videoTime = -1;
+      int ret;
+      oldPageOffset = mPageOffset;
+      while ((mVorbisState && audioTime == -1) ||
+             (mTheoraState && videoTime == -1)) {
+      
+        // Add the page to its codec state, determine its granule time.
+        PRUint32 serial = ogg_page_serialno(&page);
+        nsOggCodecState* codecState = nsnull;
+        mCodecStates.Get(serial, &codecState);
+        if (codecState && codecState->mActive) {
+          ret = ogg_stream_pagein(&codecState->mState, &page);
+          NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
+        }      
+
+        ogg_int64_t granulepos = ogg_page_granulepos(&page);
+
+        if (HasAudio() &&
+            granulepos != -1 &&
+            serial == mVorbisState->mSerial &&
+            audioTime == -1) {
+          audioTime = mVorbisState->Time(granulepos);
+        }
+        
+        if (HasVideo() &&
+            granulepos != -1 &&
+            serial == mTheoraState->mSerial &&
+            videoTime == -1) {
+          videoTime = mTheoraState->StartTime(granulepos);
+        }
+
+        mPageOffset += page.header_len + page.body_len;
+        if (ReadOggPage(&page) == -1) {
+          break;
+        }
+      }
+
+      if ((HasAudio() && audioTime == -1) ||
+          (HasVideo() && videoTime == -1)) 
+      {
+        backsteps = NS_MIN(backsteps + 1, maxBackStep);
+        backoff = PAGE_STEP * pow(2.0, backsteps);
+        continue;
+      }
+
+      // We've found appropriate time stamps here. Proceed to bisect
+      // the search space.
+      granuleTime = NS_MAX(audioTime, videoTime);
+      NS_ASSERTION(granuleTime > 0, "Must get a granuletime");
+      break;
+    }
+
+    if (interval == 0) {
+      // Seek termination condition; we've found the page boundary of the
+      // last page before the target, and the first page after the target.
+      SEEK_LOG(PR_LOG_DEBUG, ("Seek loop (interval == 0) break"));
+      NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
+      stream->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
+      mPageOffset = startOffset;
+      if (NS_FAILED(ResetDecode())) {
+        return NS_ERROR_FAILURE;
+      }
+      break;
+    }
+
+    SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lldms", guess, granuleTime));
+    if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
+      // We're within the fuzzy region in which we want to terminate the search.
+      stream->Seek(nsISeekableStream::NS_SEEK_SET, oldPageOffset);
+      mPageOffset = oldPageOffset;
+      if (NS_FAILED(ResetDecode())) {
+        return NS_ERROR_FAILURE;
+      }
+      break;
+    }
+
+    if (granuleTime >= seekTarget) {
+      // We've landed after the seek target.
+      ogg_int64_t old_offset_end = endOffset;
+      endOffset = pageOffset;
+      NS_ASSERTION(endOffset < old_offset_end, "offset_end must decrease");
+      endTime = granuleTime;
+    } else if (granuleTime < seekTarget) {
+      // Landed before seek target.
+      ogg_int64_t old_offset_start = startOffset;
+      startOffset = pageOffset;
+      startLength = pageLength;
+      NS_ASSERTION(startOffset > old_offset_start, "offset_start must increase");
+      startTime = granuleTime;
+    }
+    NS_ASSERTION(startTime < seekTarget, "Must be before seek target");
+    NS_ASSERTION(endTime >= seekTarget, "End must be after seek target");
+  }
+
+  SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops));
+
+  return NS_OK;
+}
+
+
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggReader.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+#if !defined(nsOggReader_h_)
+#define nsOggReader_h_
+
+#include <ogg/ogg.h>
+#include <theora/theoradec.h>
+#include <vorbis/codec.h>
+#include "nsBuiltinDecoderReader.h"
+#include "nsOggCodecState.h"
+
+class nsMediaDecoder;
+
+class nsOggReader : public nsBuiltinDecoderReader
+{
+public:
+  nsOggReader(nsBuiltinDecoder* aDecoder);
+  ~nsOggReader();
+
+  virtual nsresult Init();
+  virtual nsresult ResetDecode();
+  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 PRInt64 FindEndTime(PRInt64 aEndOffset);
+
+  virtual PRBool HasAudio()
+  {
+    mozilla::MonitorAutoEnter mon(mMonitor);
+    return mVorbisState != 0 && mVorbisState->mActive;
+  }
+
+  virtual PRBool HasVideo()
+  {
+    mozilla::MonitorAutoEnter mon(mMonitor);
+    return mTheoraState != 0 && mTheoraState->mActive;
+  }
+
+  virtual nsresult ReadMetadata(nsVideoInfo& aInfo);
+  virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime);
+
+private:
+  // Decodes one packet of Vorbis data, storing the resulting chunks of
+  // PCM samples in aChunks.
+  nsresult DecodeVorbis(nsTArray<SoundData*>& aChunks,
+                        ogg_packet* aPacket);
+
+  // May return NS_ERROR_OUT_OF_MEMORY.
+  nsresult DecodeTheora(nsTArray<VideoData*>& aFrames,
+                        ogg_packet* aPacket);
+
+  // Read a page of data from the Ogg file. Returns the offset of the start
+  // of the page, or -1 if the page read failed.
+  PRInt64 ReadOggPage(ogg_page* aPage);
+
+  // Read a packet for an Ogg bitstream/codec state. Returns PR_TRUE on
+  // success, or PR_FALSE if the read failed.
+  PRBool ReadOggPacket(nsOggCodecState* aCodecState, ogg_packet* aPacket);
+
+  // Performs a seek bisection to move the media stream's read cursor to the
+  // last ogg page boundary which has end time before aTarget ms 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 ms of leniency we'll allow; we'll terminate the
+  // seek when we land in the range (aTime - aFuzz, aTime) ms.
+  nsresult SeekBisection(PRInt64 aTarget,
+                         const ByteRange& aRange,
+                         PRUint32 aFuzz);
+
+private:
+  // Maps Ogg serialnos to nsOggStreams.
+  nsClassHashtable<nsUint32HashKey, nsOggCodecState> mCodecStates;
+
+  // Decode state of the Theora bitstream we're decoding, if we have video.
+  nsTheoraState* mTheoraState;
+
+  // Decode state of the Vorbis bitstream we're decoding, if we have audio.
+  nsVorbisState* mVorbisState;
+
+  // Ogg decoding state.
+  ogg_sync_state mOggState;
+
+  // The offset of the end of the last page we've read, or the start of
+  // the page we're about to read.
+  PRInt64 mPageOffset;
+
+  // Number of milliseconds of data video/audio data held in a frame.
+  PRUint32 mCallbackPeriod;
+
+  // The granulepos of the last decoded Theora frame.
+  PRInt64 mTheoraGranulepos;
+
+  // The granulepos of the last decoded Vorbis sample.
+  PRInt64 mVorbisGranulepos;
+};
+
+#endif