Bug 650994 - Decode ogg packets lazily, timestamp them at demux time. r=doublec
authorChris Pearce <chris@pearce.org.nz>
Sun, 08 May 2011 18:24:09 +1200
changeset 69341 a8f07cad55e2b2c45cc9dd74aa8e9778461f9340
parent 69340 e3a50c1f8ea9fd550973afcd3c41c0c39f65a838
child 69342 23430875e8618270202e44fe79aef56365a0e751
push id76
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:00:57 +0000
treeherdermozilla-beta@d3a2732c35f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdoublec
bugs650994
milestone6.0a1
first release with
nightly linux32
a8f07cad55e2 / 6.0a1 / 20110508030616 / files
nightly linux64
a8f07cad55e2 / 6.0a1 / 20110508030616 / files
nightly mac
a8f07cad55e2 / 6.0a1 / 20110508030616 / files
nightly win32
a8f07cad55e2 / 6.0a1 / 20110508030616 / files
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
Bug 650994 - Decode ogg packets lazily, timestamp them at demux time. r=doublec
content/base/test/file_mozfiledataurl_inner.html
content/base/test/test_mozfiledataurl.html
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/ogg/nsOggCodecState.cpp
content/media/ogg/nsOggCodecState.h
content/media/ogg/nsOggReader.cpp
content/media/ogg/nsOggReader.h
content/media/raw/nsRawReader.cpp
--- a/content/base/test/file_mozfiledataurl_inner.html
+++ b/content/base/test/file_mozfiledataurl_inner.html
@@ -57,17 +57,17 @@ function iframeNotifyParent(e) {
 }
 
 onload = function() {
   img = document.getElementById('img');
   img.onerror = img.onload = imgNotifyParent;
   iframe = document.getElementById('iframe');
   iframe.onerror = iframe.onload = iframeNotifyParent;
   audio = document.getElementById('audio');
-  audio.onerror = audio.oncanplay = audioNotifyParent;
+  audio.onerror = audio.onloadeddata = audioNotifyParent;
 }
 
 </script>
 <body>
 <img id=img>
 <audio id=audio>
 <iframe id=iframe></iframe>
 </html>
--- a/content/base/test/test_mozfiledataurl.html
+++ b/content/base/test/test_mozfiledataurl.html
@@ -7,17 +7,17 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="gen.next()">
 <p id="display">
 <iframe id=inner></iframe>
 <iframe id=iframe></iframe>
 <img id=img onload="gen.send(event);">
-<audio id=audio oncanplay="gen.send(event);">
+<audio id=audio onloadeddata="gen.send(event);">
 <input type=file id=fileList>
 </p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
@@ -85,21 +85,21 @@ function runTest() {
   isnot(res.width, 120, "correct error width");
   isnot(res.height, 90, "correct error height");
 
   // Attempt to load an audio in this document
   var file = getFile("file_mozfiledataurl_audio.ogg");
   var fileurl = URL.createObjectURL(file);
   audio.src = fileurl;
   var e = (yield);
-  is(e.type, "canplay", "loaded successfully");
+  is(e.type, "loadeddata", "loaded successfully");
 
   // Revoke url and attempt to load a audio in this document
   audio.src = "file_mozfiledataurl_audio.ogg";
-  is((yield).type, "canplay", "successfully reset audio");
+  is((yield).type, "loadeddata", "successfully reset audio");
   URL.revokeObjectURL(fileurl);
   todo(false, "urls need to act like 404s, not fail to parse");
 /*  img.src = fileurl;
   var e = (yield);
   is(e.type, "error", "failed successfully");
   isnot(img.width, 120, "correct error width");
   isnot(img.height, 90, "correct error height");
 */
@@ -108,17 +108,17 @@ function runTest() {
   var fileurl = URL.createObjectURL(file);
   isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
 
   // Attempt to load an audio in a different same-origin document
   inner.src = innerSameSiteURI;
   yield;
   inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
   var res = (yield);
-  is(res.type, "canplay", "loaded successfully");
+  is(res.type, "loadeddata", "loaded successfully");
   
   // Attempt to load an audio in a different cross-origin document
   inner.src = innerCrossSiteURI;
   yield;
   inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
   var res = (yield);
   is(res.type, "error", "failed successfully");
 
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -305,17 +305,17 @@ void nsBuiltinDecoderStateMachine::Decod
         ((!audioPump && audioPlaying && GetDecodedAudioDuration() < lowAudioThreshold) ||
          (!videoPump &&
            videoPlaying &&
            static_cast<PRUint32>(videoQueue.GetSize()) < LOW_VIDEO_FRAMES)) &&
         !HasLowUndecodedData())
 
     {
       skipToNextKeyframe = PR_TRUE;
-      LOG(PR_LOG_DEBUG, ("Skipping video decode to the next keyframe"));
+      LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder));
     }
 
     // Video decode.
     if (videoPlaying &&
         static_cast<PRUint32>(videoQueue.GetSize()) < AMPLE_VIDEO_FRAMES)
     {
       // Time the video decode, so that if it's slow, we can increase our low
       // audio threshold to reduce the chance of an audio underrun while we're
@@ -385,30 +385,30 @@ void nsBuiltinDecoderStateMachine::Decod
   if (!mStopDecodeThreads &&
       mState != DECODER_STATE_SHUTDOWN &&
       mState != DECODER_STATE_SEEKING)
   {
     mState = DECODER_STATE_COMPLETED;
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
 
-  LOG(PR_LOG_DEBUG, ("Shutting down DecodeLoop this=%p", this));
+  LOG(PR_LOG_DEBUG, ("%p Shutting down DecodeLoop this=%p", mDecoder, this));
 }
 
 PRBool nsBuiltinDecoderStateMachine::IsPlaying()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   return !mPlayStartTime.IsNull();
 }
 
 void nsBuiltinDecoderStateMachine::AudioLoop()
 {
   NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
-  LOG(PR_LOG_DEBUG, ("Begun audio thread/loop"));
+  LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder));
   PRInt64 audioDuration = 0;
   PRInt64 audioStartTime = -1;
   PRUint32 channels, rate;
   double volume = -1;
   PRBool setVolume;
   PRInt32 minWriteSamples = -1;
   PRInt64 samplesAtLastSleep = 0;
   {
@@ -581,17 +581,17 @@ void nsBuiltinDecoderStateMachine::Audio
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mAudioCompleted = PR_TRUE;
     UpdateReadyState();
     // Kick the decode and state machine threads; they may be sleeping waiting
     // for this to finish.
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
-  LOG(PR_LOG_DEBUG, ("Audio stream finished playing, audio thread exit"));
+  LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder));
 }
 
 PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples,
                                                    PRUint32 aChannels,
                                                    PRUint64 aSampleOffset)
 
 {
   ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
@@ -1207,17 +1207,17 @@ nsresult nsBuiltinDecoderStateMachine::R
             }
           }
         }
         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));
+        LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n", mDecoder, mCurrentFrameTime));
 
         // Change state to DECODING or COMPLETED now. SeekingStopped will
         // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
         // if we need to seek again.
         
         nsCOMPtr<nsIRunnable> stopEvent;
         if (GetMediaTime() == mEndTime) {
           LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
@@ -1316,17 +1316,17 @@ nsresult nsBuiltinDecoderStateMachine::R
           // is created. The StopPlayback call also resets the IsPlaying() state
           // so audio is restarted correctly.
           StopPlayback(AUDIO_SHUTDOWN);
         }
 
         if (mState != DECODER_STATE_COMPLETED)
           continue;
 
-        LOG(PR_LOG_DEBUG, ("Shutting down the state machine thread"));
+        LOG(PR_LOG_DEBUG, ("%p Shutting down the state machine thread", mDecoder));
         StopDecodeThreads();
 
         if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
           PRInt64 videoTime = HasVideo() ? mVideoFrameEndTime : 0;
           PRInt64 clockTime = NS_MAX(mEndTime, NS_MAX(videoTime, GetAudioClock()));
           UpdatePlaybackPosition(clockTime);
           {
             ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -1626,17 +1626,17 @@ void nsBuiltinDecoderStateMachine::Updat
 }
 
 void nsBuiltinDecoderStateMachine::LoadMetadata()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
-  LOG(PR_LOG_DEBUG, ("Loading Media Headers"));
+  LOG(PR_LOG_DEBUG, ("%p Loading Media Headers", mDecoder));
   nsresult res;
   nsVideoInfo info;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     res = mReader->ReadMetadata(&info);
   }
   mInfo = info;
 
@@ -1677,20 +1677,21 @@ void nsBuiltinDecoderStateMachine::Start
   // there might be pending main-thread events, such as "data
   // received" notifications, that mean we're not actually still
   // buffering by the time this runnable executes. So instead
   // we just trigger UpdateReadyStateForData; when it runs, it
   // will check the current state and decide whether to tell
   // the element we're buffering or not.
   UpdateReadyState();
   mState = DECODER_STATE_BUFFERING;
-  LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING, decoded for %.3lfs",
-                     decodeDuration.ToSeconds()));
+  LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs",
+                     mDecoder, decodeDuration.ToSeconds()));
   nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
-  LOG(PR_LOG_DEBUG, ("Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
+  LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
+    mDecoder,
     stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
     stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)"));
 }
 
 nsresult nsBuiltinDecoderStateMachine::GetBuffered(nsTimeRanges* aBuffered) {
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
   stream->Pin();
--- a/content/media/ogg/nsOggCodecState.cpp
+++ b/content/media/ogg/nsOggCodecState.cpp
@@ -49,86 +49,177 @@ extern PRLogModuleInfo* gBuiltinDecoderL
 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
 nsOggCodecState*
 nsOggCodecState::Create(ogg_page* aPage)
 {
+  NS_ASSERTION(ogg_page_bos(aPage), "Only call on BOS page!");
   nsAutoPtr<nsOggCodecState> codecState;
   if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
     codecState = new nsTheoraState(aPage);
   } else if (aPage->body_len > 6 && memcmp(aPage->body+1, "vorbis", 6) == 0) {
     codecState = new nsVorbisState(aPage);
   } else if (aPage->body_len > 8 && memcmp(aPage->body, "fishead\0", 8) == 0) {
     codecState = new nsSkeletonState(aPage);
   } else {
-    codecState = new nsOggCodecState(aPage);
+    codecState = new nsOggCodecState(aPage, PR_FALSE);
   }
   return codecState->nsOggCodecState::Init() ? codecState.forget() : nsnull;
 }
 
-nsOggCodecState::nsOggCodecState(ogg_page* aBosPage) :
+nsOggCodecState::nsOggCodecState(ogg_page* aBosPage, PRBool aActive) :
   mPacketCount(0),
   mSerial(ogg_page_serialno(aBosPage)),
-  mActive(PR_FALSE),
-  mDoneReadingHeaders(PR_FALSE)
+  mActive(aActive),
+  mDoneReadingHeaders(!aActive)
 {
   MOZ_COUNT_CTOR(nsOggCodecState);
   memset(&mState, 0, sizeof(ogg_stream_state));
 }
 
 nsOggCodecState::~nsOggCodecState() {
   MOZ_COUNT_DTOR(nsOggCodecState);
+  Reset();
 #ifdef DEBUG
   int ret =
 #endif
-    ogg_stream_clear(&mState);
+  ogg_stream_clear(&mState);
   NS_ASSERTION(ret == 0, "ogg_stream_clear failed");
 }
 
 nsresult nsOggCodecState::Reset() {
   if (ogg_stream_reset(&mState) != 0) {
     return NS_ERROR_FAILURE;
   }
-  mBuffer.Erase();
+  mPackets.Erase();
+  ClearUnstamped();
   return NS_OK;
 }
 
+void nsOggCodecState::ClearUnstamped()
+{
+  for (PRUint32 i = 0; i < mUnstamped.Length(); ++i) {
+    nsOggCodecState::ReleasePacket(mUnstamped[i]);
+  }
+  mUnstamped.Clear();
+}
+
 PRBool nsOggCodecState::Init() {
   int ret = ogg_stream_init(&mState, mSerial);
   return ret == 0;
 }
 
-void nsPageQueue::Append(ogg_page* aPage) {
-  ogg_page* p = new ogg_page();
-  p->header_len = aPage->header_len;
-  p->body_len = aPage->body_len;
-  p->header = new unsigned char[p->header_len + p->body_len];
-  p->body = p->header + p->header_len;
-  memcpy(p->header, aPage->header, p->header_len);
-  memcpy(p->body, aPage->body, p->body_len);
-  nsDeque::Push(p);
+void nsVorbisState::RecordVorbisPacketSamples(ogg_packet* aPacket,
+                                              long aSamples)
+{
+#ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
+  mVorbisPacketSamples[aPacket] = aSamples;
+#endif
+}
+
+void nsVorbisState::ValidateVorbisPacketSamples(ogg_packet* aPacket,
+                                                long aSamples)
+{
+#ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
+  NS_ASSERTION(mVorbisPacketSamples[aPacket] == aSamples,
+    "Decoded samples for Vorbis packet don't match expected!");
+  mVorbisPacketSamples.erase(aPacket);
+#endif
+}
+
+void nsVorbisState::AssertHasRecordedPacketSamples(ogg_packet* aPacket)
+{
+#ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
+  NS_ASSERTION(mVorbisPacketSamples.count(aPacket) == 1,
+    "Must have recorded packet samples");
+#endif
+}
+
+static ogg_packet* Clone(ogg_packet* aPacket) {
+  ogg_packet* p = new ogg_packet();
+  memcpy(p, aPacket, sizeof(ogg_packet));
+  p->packet = new unsigned char[p->bytes];
+  memcpy(p->packet, aPacket->packet, p->bytes);
+  return p;
+}
+
+void nsOggCodecState::ReleasePacket(ogg_packet* aPacket) {
+  if (aPacket)
+    delete [] aPacket->packet;
+  delete aPacket;
+}
+
+void nsPacketQueue::Append(ogg_packet* aPacket) {
+  nsDeque::Push(aPacket);
+}
+
+ogg_packet* nsOggCodecState::PacketOut() {
+  if (mPackets.IsEmpty()) {
+    return nsnull;
+  }
+  return mPackets.PopFront();
 }
 
-PRBool nsOggCodecState::PageInFromBuffer() {
-  if (mBuffer.IsEmpty())
-    return PR_FALSE;
-  ogg_page *p = mBuffer.PeekFront();
-  int ret = ogg_stream_pagein(&mState, p);
-  NS_ENSURE_TRUE(ret == 0, PR_FALSE);
-  mBuffer.PopFront();
-  delete [] p->header;
-  delete p;
-  return PR_TRUE;
+nsresult nsOggCodecState::PageIn(ogg_page* aPage) {
+  if (!mActive)
+    return NS_OK;
+  NS_ASSERTION(ogg_page_serialno(aPage) == mSerial, "Page must be for this stream!");
+  if (ogg_stream_pagein(&mState, aPage) == -1)
+    return NS_ERROR_FAILURE;
+  int r;
+  do {
+    ogg_packet packet;
+    r = ogg_stream_packetout(&mState, &packet);
+    if (r == 1) {
+      mPackets.Append(Clone(&packet));
+    }
+  } while (r != 0);
+  if (ogg_stream_check(&mState)) {
+    NS_WARNING("Unrecoverable error in ogg_stream_packetout");
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+PRBool
+nsOggCodecState::PacketOutUntilGranulepos()
+{
+  int r;
+  PRBool foundGp = PR_FALSE;
+  // Extract packets from the sync state until either no more packets
+  // come out, or we get a data packet with non -1 granulepos.
+  do {
+    ogg_packet packet;
+    r = ogg_stream_packetout(&mState, &packet);
+    if (r == 1) {
+      ogg_packet* clone = Clone(&packet);
+      if (IsHeader(&packet)) {
+        // Header packets go straight into the packet queue.
+        mPackets.Append(clone);
+      } else {
+        // We buffer data packets until we encounter a granulepos. We'll
+        // then use the granulepos to figure out the granulepos of the
+        // preceeding packets.
+        mUnstamped.AppendElement(clone);
+        foundGp = packet.granulepos != -1;
+      }
+    }
+  } while (r != 0 && !foundGp);
+  if (ogg_stream_check(&mState)) {
+    NS_WARNING("Unrecoverable error in ogg_stream_packetout");
+    return NS_ERROR_FAILURE;
+  }
+  return foundGp;
 }
 
 nsTheoraState::nsTheoraState(ogg_page* aBosPage) :
-  nsOggCodecState(aBosPage),
+  nsOggCodecState(aBosPage, PR_TRUE),
   mSetup(0),
   mCtx(0),
   mPixelAspectRatio(0)
 {
   MOZ_COUNT_CTOR(nsTheoraState);
   th_info_init(&mInfo);
   th_comment_init(&mComment);
 }
@@ -206,16 +297,21 @@ nsTheoraState::DecodeHeader(ogg_packet* 
 PRInt64
 nsTheoraState::Time(PRInt64 granulepos) {
   if (!mActive) {
     return -1;
   }
   return nsTheoraState::Time(&mInfo, granulepos);
 }
 
+PRBool
+nsTheoraState::IsHeader(ogg_packet* aPacket) {
+  return th_packet_isheader(aPacket);
+}
+
 # define TH_VERSION_CHECK(_info,_maj,_min,_sub) \
  ((_info)->version_major>(_maj)||(_info)->version_major==(_maj)&& \
  ((_info)->version_minor>(_min)||(_info)->version_minor==(_min)&& \
  (_info)->version_subminor>=(_sub)))
 
 PRInt64 nsTheoraState::Time(th_info* aInfo, PRInt64 aGranulepos)
 {
   if (aGranulepos < 0 || aInfo->fps_numerator == 0) {
@@ -267,40 +363,168 @@ nsTheoraState::MaxKeyframeOffset()
   PRInt64 d = 0; // d will be 0 if multiplication overflows.
   MulOverflow(USECS_PER_S, mInfo.fps_denominator, d);
   frameDuration = d / mInfo.fps_numerator;
 
   // Total time in usecs keyframe can be offset from any given frame.
   return frameDuration * keyframeDiff;
 }
 
+nsresult
+nsTheoraState::PageIn(ogg_page* aPage)
+{
+  if (!mActive)
+    return NS_OK;
+  NS_ASSERTION(static_cast<PRUint32>(ogg_page_serialno(aPage)) == mSerial,
+               "Page must be for this stream!");
+  if (ogg_stream_pagein(&mState, aPage) == -1)
+    return NS_ERROR_FAILURE;
+  PRBool foundGp = PacketOutUntilGranulepos();
+  if (foundGp && mDoneReadingHeaders) {
+    // We've found a packet with a granulepos, and we've loaded our metadata
+    // and initialized our decoder. Determine granulepos of buffered packets.
+    ReconstructTheoraGranulepos();
+    for (PRUint32 i = 0; i < mUnstamped.Length(); ++i) {
+      ogg_packet* packet = mUnstamped[i];
+#ifdef DEBUG
+      NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
+      NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
+#endif
+      mPackets.Append(packet);
+    }
+    mUnstamped.Clear();
+  }
+  return NS_OK;
+}
+
+// Returns 1 if the Theora info struct is decoding a media of Theora
+// verion (maj,min,sub) or later, otherwise returns 0.
+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;
+}
+
+void nsTheoraState::ReconstructTheoraGranulepos()
+{
+  if (mUnstamped.Length() == 0) {
+    return;
+  }
+  ogg_int64_t lastGranulepos = mUnstamped[mUnstamped.Length() - 1]->granulepos;
+  NS_ASSERTION(lastGranulepos != -1, "Must know last granulepos");
+
+  // Reconstruct the granulepos (and thus timestamps) of the decoded
+  // frames. Granulepos are stored as ((keyframe<<shift)+offset). We
+  // know the granulepos of the last frame in the list, so we can infer
+  // the granulepos of the intermediate frames using their frame numbers.
+  ogg_int64_t shift = mInfo.keyframe_granule_shift;
+  ogg_int64_t version_3_2_1 = TheoraVersion(&mInfo,3,2,1);
+  ogg_int64_t lastFrame = th_granule_frame(mCtx,
+                                           lastGranulepos) + version_3_2_1;
+  ogg_int64_t firstFrame = lastFrame - mUnstamped.Length() + 1;
+
+  // Until we encounter a keyframe, we'll assume that the "keyframe"
+  // segment of the granulepos is the first frame, or if that causes
+  // the "offset" segment to overflow, we assume the required
+  // keyframe is maximumally offset. Until we encounter a keyframe
+  // the granulepos will probably be wrong, but we can't decode the
+  // frame anyway (since we don't have its keyframe) so it doesn't really
+  // matter.
+  ogg_int64_t keyframe = lastGranulepos >> shift;
+
+  // The lastFrame, firstFrame, keyframe variables, as well as the frame
+  // variable in the loop below, store the frame number for Theora
+  // version >= 3.2.1 streams, and store the frame index for Theora
+  // version < 3.2.1 streams.
+  for (PRUint32 i = 0; i < mUnstamped.Length() - 1; ++i) {
+    ogg_int64_t frame = firstFrame + i;
+    ogg_int64_t granulepos;
+    ogg_packet* packet = mUnstamped[i];
+    PRBool isKeyframe = th_packet_iskeyframe(packet) == 1;
+
+    if (isKeyframe) {
+      granulepos = frame << shift;
+      keyframe = frame;
+    } else if (frame >= keyframe &&
+                frame - keyframe < ((ogg_int64_t)1 << shift))
+    {
+      // (frame - keyframe) won't overflow the "offset" segment of the
+      // granulepos, so it's safe to calculate the granulepos.
+      granulepos = (keyframe << shift) + (frame - keyframe);
+    } else {
+      // (frame - keyframeno) will overflow the "offset" segment of the
+      // granulepos, so we take "keyframe" to be the max possible offset
+      // frame instead.
+      ogg_int64_t k = NS_MAX(frame - (((ogg_int64_t)1 << shift) - 1), version_3_2_1);
+      granulepos = (k << shift) + (frame - k);
+    }
+    // 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(granulepos >= version_3_2_1,
+                  "Invalid granulepos for Theora version");
+
+    // Check that the frame's granule number is one more than the
+    // previous frame's.
+    NS_ASSERTION(i == 0 ||
+                 th_granule_frame(mCtx, granulepos) ==
+                 th_granule_frame(mCtx, mUnstamped[i-1]->granulepos) + 1,
+                 "Granulepos calculation is incorrect!");
+
+    packet->granulepos = granulepos;
+  }
+
+  // Check that the second to last frame's granule number is one less than
+  // the last frame's (the known granule number). If not our granulepos
+  // recovery missed a beat.
+  NS_ASSERTION(mUnstamped.Length() < 2 ||
+    th_granule_frame(mCtx, mUnstamped[mUnstamped.Length()-2]->granulepos) + 1 ==
+    th_granule_frame(mCtx, lastGranulepos),
+    "Granulepos recovery should catch up with packet->granulepos!");
+}
+
 nsresult nsVorbisState::Reset()
 {
   nsresult res = NS_OK;
   if (mActive && vorbis_synthesis_restart(&mDsp) != 0) {
     res = NS_ERROR_FAILURE;
   }
   if (NS_FAILED(nsOggCodecState::Reset())) {
     return NS_ERROR_FAILURE;
   }
+
+  mGranulepos = 0;
+  mPrevVorbisBlockSize = 0;
+
   return res;
 }
 
 nsVorbisState::nsVorbisState(ogg_page* aBosPage) :
-  nsOggCodecState(aBosPage)
+  nsOggCodecState(aBosPage, PR_TRUE),
+  mPrevVorbisBlockSize(0),
+  mGranulepos(0)
 {
   MOZ_COUNT_CTOR(nsVorbisState);
   vorbis_info_init(&mInfo);
   vorbis_comment_init(&mComment);
   memset(&mDsp, 0, sizeof(vorbis_dsp_state));
   memset(&mBlock, 0, sizeof(vorbis_block));
 }
 
 nsVorbisState::~nsVorbisState() {
   MOZ_COUNT_DTOR(nsVorbisState);
+  Reset();
   vorbis_block_clear(&mBlock);
   vorbis_dsp_clear(&mDsp);
   vorbis_info_clear(&mInfo);
   vorbis_comment_clear(&mComment);
 }
 
 PRBool nsVorbisState::DecodeHeader(ogg_packet* aPacket) {
   mPacketCount++;
@@ -322,22 +546,23 @@ PRBool nsVorbisState::DecodeHeader(ogg_p
   // For more details of the Vorbis/Ogg containment scheme, see the Vorbis I
   // Specification, Chapter 4, Codec Setup and Packet Decode:
   // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-580004
 
   PRBool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x5;
 
   if (ret < 0 || mPacketCount > 3) {
     // We've received an error, or the first three packets weren't valid
-    // header packets, assume bad input, and don't activate the bitstream.
+    // header packets, assume bad input, and deactivate the bitstream.
     mDoneReadingHeaders = PR_TRUE;
+    mActive = PR_FALSE;
   } else if (ret == 0 && isSetupHeader && mPacketCount == 3) {
-    // Successfully read the three header packets, activate the bitstream.
+    // Successfully read the three header packets.
+    // The bitstream remains active.
     mDoneReadingHeaders = PR_TRUE;
-    mActive = PR_TRUE;
   }
   return mDoneReadingHeaders;
 }
 
 PRBool nsVorbisState::Init()
 {
   if (!mActive)
     return PR_FALSE;
@@ -372,18 +597,161 @@ PRInt64 nsVorbisState::Time(vorbis_info*
   if (aGranulepos == -1 || aInfo->rate == 0) {
     return -1;
   }
   PRInt64 t = 0;
   MulOverflow(USECS_PER_S, aGranulepos, t);
   return t / aInfo->rate;
 }
 
+PRBool
+nsVorbisState::IsHeader(ogg_packet* aPacket)
+{
+  // The first byte in each Vorbis header packet is either 0x01, 0x03, or 0x05,
+  // i.e. the first bit is odd. Audio data packets have their first bit as 0x0.
+  // Any packet with its first bit set cannot be a data packet, it's a
+  // (possibly invalid) header packet.
+  // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2.1
+  return aPacket->bytes > 0 ? (aPacket->packet[0] & 0x1) : PR_FALSE;
+}
+
+nsresult
+nsVorbisState::PageIn(ogg_page* aPage)
+{
+  if (!mActive)
+    return NS_OK;
+  NS_ASSERTION(static_cast<PRUint32>(ogg_page_serialno(aPage)) == mSerial,
+               "Page must be for this stream!");
+  if (ogg_stream_pagein(&mState, aPage) == -1)
+    return NS_ERROR_FAILURE;
+  PRBool foundGp = PacketOutUntilGranulepos();
+  if (foundGp && mDoneReadingHeaders) {
+    // We've found a packet with a granulepos, and we've loaded our metadata
+    // and initialized our decoder. Determine granulepos of buffered packets.
+    ReconstructVorbisGranulepos();
+    for (PRUint32 i = 0; i < mUnstamped.Length(); ++i) {
+      ogg_packet* packet = mUnstamped[i];
+      AssertHasRecordedPacketSamples(packet);
+      NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
+      NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
+      mPackets.Append(packet);
+    }
+    mUnstamped.Clear();
+  }
+  return NS_OK;
+}
+
+nsresult nsVorbisState::ReconstructVorbisGranulepos()
+{
+  // The number of samples in a Vorbis packet is:
+  // window_blocksize(previous_packet)/4+window_blocksize(current_packet)/4
+  // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-230001.3.2
+  // So we maintain mPrevVorbisBlockSize, the block size of the last packet
+  // encountered. We also maintain mGranulepos, which is the granulepos of
+  // the last encountered packet. This enables us to give granulepos to
+  // packets when the last packet in mUnstamped doesn't have a granulepos
+  // (for example if the stream was truncated).
+  //
+  // We validate our prediction of the number of samples decoded when
+  // VALIDATE_VORBIS_SAMPLE_CALCULATION is defined by recording the predicted
+  // number of samples, and verifing we extract that many when decoding
+  // each packet.
+
+  NS_ASSERTION(mUnstamped.Length() > 0, "Length must be > 0");
+  ogg_packet* last = mUnstamped[mUnstamped.Length()-1];
+  NS_ASSERTION(last->e_o_s || last->granulepos >= 0,
+    "Must know last granulepos!");
+  if (mUnstamped.Length() == 1) {
+    ogg_packet* packet = mUnstamped[0];
+    long blockSize = vorbis_packet_blocksize(&mInfo, packet);
+    if (blockSize < 0) {
+      // On failure vorbis_packet_blocksize returns < 0. If we've got
+      // a bad packet, we just assume that decode will have to skip this
+      // packet, i.e. assume 0 samples are decodable from this packet.
+      blockSize = 0;
+      mPrevVorbisBlockSize = 0;
+    }
+    long samples = mPrevVorbisBlockSize / 4 + blockSize / 4;
+    mPrevVorbisBlockSize = blockSize;
+    if (packet->granulepos == -1) {
+      packet->granulepos = mGranulepos + samples;
+    }
+    mGranulepos = packet->granulepos;
+    RecordVorbisPacketSamples(packet, samples);
+    return NS_OK;
+  }
+
+  PRBool unknownGranulepos = last->granulepos == -1;
+  int totalSamples = 0;
+  for (PRInt32 i = mUnstamped.Length() - 1; i > 0; i--) {
+    ogg_packet* packet = mUnstamped[i];
+    ogg_packet* prev = mUnstamped[i-1];
+    ogg_int64_t granulepos = packet->granulepos;
+    NS_ASSERTION(granulepos != -1, "Must know granulepos!");
+    long prevBlockSize = vorbis_packet_blocksize(&mInfo, prev);
+    long blockSize = vorbis_packet_blocksize(&mInfo, packet);
+
+    if (blockSize < 0 || prevBlockSize < 0) {
+      // On failure vorbis_packet_blocksize returns < 0. If we've got
+      // a bad packet, we just assume that decode will have to skip this
+      // packet, i.e. assume 0 samples are decodable from this packet.
+      blockSize = 0;
+      prevBlockSize = 0;
+    }
+
+    long samples = prevBlockSize / 4 + blockSize / 4;
+    totalSamples += samples;
+    prev->granulepos = granulepos - samples;
+    RecordVorbisPacketSamples(packet, samples);
+  }
+
+  if (unknownGranulepos) {
+    for (PRUint32 i = 0; i < mUnstamped.Length(); i++) {
+      ogg_packet* packet = mUnstamped[i];
+      packet->granulepos += mGranulepos + totalSamples + 1;
+    }
+  }
+
+  ogg_packet* first = mUnstamped[0];
+  long blockSize = vorbis_packet_blocksize(&mInfo, first);
+  if (blockSize < 0) {
+    mPrevVorbisBlockSize = 0;
+    blockSize = 0;
+  }
+
+  long samples = (mPrevVorbisBlockSize == 0) ? 0 :
+                  mPrevVorbisBlockSize / 4 + blockSize / 4;
+  PRInt64 start = first->granulepos - samples;
+  RecordVorbisPacketSamples(first, samples);
+
+  if (last->e_o_s && start < mGranulepos) {
+    // We've calculated that there are more samples in this page than its
+    // granulepos claims, and it's the last page in the stream. This is legal,
+    // and we will need to prune the trailing samples when we come to decode it.
+    // We must correct the timestamps so that they follow the last Vorbis page's
+    // samples.
+    PRInt64 pruned = mGranulepos - start;
+    for (PRUint32 i = 0; i < mUnstamped.Length() - 1; i++) {
+      mUnstamped[i]->granulepos += pruned;
+    }
+#ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
+    mVorbisPacketSamples[last] -= pruned;
+#endif
+  }
+
+  mPrevVorbisBlockSize = vorbis_packet_blocksize(&mInfo, last);
+  mPrevVorbisBlockSize = NS_MAX(static_cast<long>(0), mPrevVorbisBlockSize);
+  mGranulepos = last->granulepos;
+
+  return NS_OK;
+}
+
+
 nsSkeletonState::nsSkeletonState(ogg_page* aBosPage)
-  : nsOggCodecState(aBosPage),
+  : nsOggCodecState(aBosPage, PR_TRUE),
     mVersion(0),
     mPresentationTime(0),
     mLength(0)
 {
   MOZ_COUNT_CTOR(nsSkeletonState);
 }
  
 nsSkeletonState::~nsSkeletonState()
--- a/content/media/ogg/nsOggCodecState.h
+++ b/content/media/ogg/nsOggCodecState.h
@@ -46,60 +46,72 @@
 #else
 #include <vorbis/codec.h>
 #endif
 #include <nsDeque.h>
 #include <nsTArray.h>
 #include <nsClassHashtable.h>
 #include "VideoUtils.h"
 
-class OggPageDeallocator : public nsDequeFunctor {
-  virtual void* operator() (void* aPage) {
-    ogg_page* p = static_cast<ogg_page*>(aPage);
-    delete [] p->header;
+// Uncomment the following to validate that we're predicting the number
+// of Vorbis samples in each packet correctly.
+#define VALIDATE_VORBIS_SAMPLE_CALCULATION
+#ifdef  VALIDATE_VORBIS_SAMPLE_CALCULATION
+#include <map>
+#endif
+
+// Deallocates a packet, used in nsPacketQueue below.
+class OggPacketDeallocator : public nsDequeFunctor {
+  virtual void* operator() (void* aPacket) {
+    ogg_packet* p = static_cast<ogg_packet*>(aPacket);
+    delete [] p->packet;
     delete p;
     return nsnull;
   }
 };
 
-// A queue of ogg_pages. When we read a page, and it's not from the bitstream
-// which we're looking for a page for, we buffer the page in the nsOggCodecState,
-// rather than pushing it immediately into the ogg_stream_state object. This
-// is because if we're skipping up to the next keyframe in very large frame
-// sized videos, there may be several megabytes of data between keyframes,
-// and the ogg_stream_state would end up resizing its buffer every time we
-// added a new 4K page to the bitstream, which kills performance on Windows.
-class nsPageQueue : private nsDeque {
+// A queue of ogg_packets. When we read a page, we extract the page's packets
+// and buffer them in the owning stream's nsOggCodecState. This is because
+// if we're skipping up to the next keyframe in very large frame sized videos,
+// there may be several megabytes of data between keyframes, and the
+// ogg_stream_state would end up resizing its buffer every time we added a
+// new 4KB page to the bitstream, which kills performance on Windows. This
+// also gives us the option to timestamp packets rather than decoded
+// frames/samples, reducing the amount of frames/samples we must decode to
+// determine start-time at a particular offset, and gives us finer control
+// over memory usage.
+class nsPacketQueue : private nsDeque {
 public:
-  nsPageQueue() : nsDeque(new OggPageDeallocator()) {}
-  ~nsPageQueue() { Erase(); }
+  nsPacketQueue() : nsDeque(new OggPacketDeallocator()) {}
+  ~nsPacketQueue() { Erase(); }
   PRBool IsEmpty() { return nsDeque::GetSize() == 0; }
-  void Append(ogg_page* aPage);
-  ogg_page* PopFront() { return static_cast<ogg_page*>(nsDeque::PopFront()); }
-  ogg_page* PeekFront() { return static_cast<ogg_page*>(nsDeque::PeekFront()); }
+  void Append(ogg_packet* aPacket);
+  ogg_packet* PopFront() { return static_cast<ogg_packet*>(nsDeque::PopFront()); }
+  ogg_packet* PeekFront() { return static_cast<ogg_packet*>(nsDeque::PeekFront()); }
+  void PushFront(ogg_packet* aPacket) { nsDeque::PushFront(aPacket); }
+  void PushBack(ogg_packet* aPacket) { nsDeque::PushFront(aPacket); }
   void Erase() { nsDeque::Erase(); }
 };
 
 // Encapsulates the data required for decoding an ogg bitstream and for
 // converting granulepos to timestamps.
 class nsOggCodecState {
- public:
+public:
   // Ogg types we know about
   enum CodecType {
     TYPE_VORBIS=0,
     TYPE_THEORA=1,
     TYPE_SKELETON=2,
     TYPE_UNKNOWN=3
   };
 
- public:
-  nsOggCodecState(ogg_page* aBosPage);
   virtual ~nsOggCodecState();
   
-  // Factory for creating nsCodecStates.
+  // Factory for creating nsCodecStates. Use instead of constructor.
+  // aPage should be a beginning-of-stream page.
   static nsOggCodecState* Create(ogg_page* aPage);
   
   virtual CodecType GetType() { return TYPE_UNKNOWN; }
   
   // Reads a header packet. Returns PR_TRUE when last header has been read.
   virtual PRBool DecodeHeader(ogg_packet* aPacket) {
     return (mDoneReadingHeaders = PR_TRUE);
   }
@@ -114,107 +126,215 @@ class nsOggCodecState {
   virtual PRBool Init();
 
   // Returns PR_TRUE when this bitstream has finished reading all its
   // header packets.
   PRBool DoneReadingHeaders() { return mDoneReadingHeaders; }
 
   // Deactivates the bitstream. Only the primary video and audio bitstreams
   // should be active.
-  void Deactivate() { mActive = PR_FALSE; }
+  void Deactivate() {
+    mActive = PR_FALSE;
+    mDoneReadingHeaders = PR_TRUE;
+    Reset();
+  }
 
   // Resets decoding state.
   virtual nsresult Reset();
 
-  // Clones a page and adds it to our buffer of pages which we'll insert to
-  // the bitstream at a later time (using PageInFromBuffer()). Memory stored in
-  // cloned pages is freed when Reset() or PageInFromBuffer() are called.
-  inline void AddToBuffer(ogg_page* aPage) { mBuffer.Append(aPage); }
+  // Returns PR_TRUE if the nsOggCodecState thinks this packet is a header
+  // packet. Note this does not verify the validity of the header packet,
+  // it just guarantees that the packet is marked as a header packet (i.e.
+  // it is definintely not a data packet). Do not use this to identify
+  // streams, use it to filter header packets from data packets while
+  // decoding.
+  virtual PRBool IsHeader(ogg_packet* aPacket) { return PR_FALSE; }
 
-  // Returns PR_TRUE if we had a buffered page and we successfully inserted it
-  // into the bitstream.
-  PRBool PageInFromBuffer();
+  // Returns the next packet in the stream, or nsnull if there are no more
+  // packets buffered in the packet queue. More packets can be buffered by
+  // inserting one or more pages into the stream by calling PageIn(). The
+  // caller is responsible for deleting returned packet's using
+  // nsOggCodecState::ReleasePacket(). The packet will have a valid granulepos.
+  ogg_packet* PacketOut();
+
+  // Releases the memory used by a cloned packet. Every packet returned by
+  // PacketOut() must be free'd using this function.
+  static void ReleasePacket(ogg_packet* aPacket);
+
+  // Extracts all packets from the page, and inserts them into the packet
+  // queue. They can be extracted by calling PacketOut(). Packets from an
+  // inactive stream are not buffered, i.e. this call has no effect for
+  // inactive streams. Multiple pages may need to be inserted before
+  // PacketOut() starts to return packets, as granulepos may need to be
+  // captured.
+  virtual nsresult PageIn(ogg_page* aPage);
 
   // Number of packets read.  
   PRUint64 mPacketCount;
 
   // Serial number of the bitstream.
   PRUint32 mSerial;
 
   // Ogg specific state.
   ogg_stream_state mState;
 
-  // Buffer of pages which we've not yet inserted into the ogg_stream_state.
-  nsPageQueue mBuffer;
+  // Queue of as yet undecoded packets. Packets are guaranteed to have
+  // a valid granulepos.
+  nsPacketQueue mPackets;
 
   // Is the bitstream active; whether we're decoding and playing this bitstream.
   PRPackedBool mActive;
   
   // PR_TRUE when all headers packets have been read.
   PRPackedBool mDoneReadingHeaders;
+
+protected:
+  // Constructs a new nsOggCodecState. aActive denotes whether the stream is
+  // active. For streams of unsupported or unknown types, aActive should be
+  // PR_FALSE.
+  nsOggCodecState(ogg_page* aBosPage, PRBool aActive);
+
+  // Deallocates all packets stored in mUnstamped, and clears the array.
+  void ClearUnstamped();
+
+  // Extracts packets out of mState until a data packet with a non -1
+  // granulepos is encountered, or no more packets are readable. Header
+  // packets are pushed into the packet queue immediately, and data packets
+  // are buffered in mUnstamped. Once a non -1 granulepos packet is read
+  // the granulepos of the packets in mUnstamped can be inferred, and they
+  // can be pushed over to mPackets. Used by PageIn() implementations in
+  // subclasses.
+  PRBool PacketOutUntilGranulepos();
+
+  // Temporary buffer in which to store packets while we're reading packets
+  // in order to capture granulepos.
+  nsTArray<ogg_packet*> mUnstamped;
 };
 
 class nsVorbisState : public nsOggCodecState {
 public:
   nsVorbisState(ogg_page* aBosPage);
   virtual ~nsVorbisState();
 
-  virtual CodecType GetType() { return TYPE_VORBIS; }
-  virtual PRBool DecodeHeader(ogg_packet* aPacket);
-  virtual PRInt64 Time(PRInt64 granulepos);
-  virtual PRBool Init();
-  virtual nsresult Reset();
+  CodecType GetType() { return TYPE_VORBIS; }
+  PRBool DecodeHeader(ogg_packet* aPacket);
+  PRInt64 Time(PRInt64 granulepos);
+  PRBool Init();
+  nsresult Reset();
+  PRBool IsHeader(ogg_packet* aPacket);
+  nsresult PageIn(ogg_page* aPage); 
 
   // Returns the end time that a granulepos represents.
   static PRInt64 Time(vorbis_info* aInfo, PRInt64 aGranulePos); 
- 
+
   vorbis_info mInfo;
   vorbis_comment mComment;
   vorbis_dsp_state mDsp;
   vorbis_block mBlock;
+
+private:
+
+  // Reconstructs the granulepos of Vorbis packets stored in the mUnstamped
+  // array.
+  nsresult ReconstructVorbisGranulepos();
+
+  // The "block size" of the previously decoded Vorbis packet, or 0 if we've
+  // not yet decoded anything. This is used to calculate the number of samples
+  // in a Vorbis packet, since each Vorbis packet depends on the previous
+  // packet while being decoded.
+  long mPrevVorbisBlockSize;
+
+  // Granulepos (end sample) of the last decoded Vorbis packet. This is used
+  // to calculate the Vorbis granulepos when we don't find a granulepos to
+  // back-propagate from.
+  PRInt64 mGranulepos;
+
+#ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
+  // When validating that we've correctly predicted Vorbis packets' number
+  // of samples, we store each packet's predicted number of samples in this
+  // map, and verify we decode the predicted number of samples.
+  std::map<ogg_packet*, long> mVorbisPacketSamples;
+#endif
+
+  // Records that aPacket is predicted to have aSamples samples.
+  // This function has no effect if VALIDATE_VORBIS_SAMPLE_CALCULATION
+  // is not defined.
+  void RecordVorbisPacketSamples(ogg_packet* aPacket, long aSamples);
+
+  // Verifies that aPacket has had its number of samples predicted.
+  // This function has no effect if VALIDATE_VORBIS_SAMPLE_CALCULATION
+  // is not defined.
+  void AssertHasRecordedPacketSamples(ogg_packet* aPacket);
+
+public:
+  // Asserts that the number of samples predicted for aPacket is aSamples.
+  // This function has no effect if VALIDATE_VORBIS_SAMPLE_CALCULATION
+  // is not defined.
+  void ValidateVorbisPacketSamples(ogg_packet* aPacket, long aSamples);
+
 };
 
+// Returns 1 if the Theora info struct is decoding a media of Theora
+// verion (maj,min,sub) or later, otherwise returns 0.
+int TheoraVersion(th_info* info,
+                  unsigned char maj,
+                  unsigned char min,
+                  unsigned char sub);
+
 class nsTheoraState : public nsOggCodecState {
 public:
   nsTheoraState(ogg_page* aBosPage);
   virtual ~nsTheoraState();
 
-  virtual CodecType GetType() { return TYPE_THEORA; }
-  virtual PRBool DecodeHeader(ogg_packet* aPacket);
-  virtual PRInt64 Time(PRInt64 granulepos);
-  virtual PRInt64 StartTime(PRInt64 granulepos);
-  virtual PRBool Init();
+  CodecType GetType() { return TYPE_THEORA; }
+  PRBool DecodeHeader(ogg_packet* aPacket);
+  PRInt64 Time(PRInt64 granulepos);
+  PRInt64 StartTime(PRInt64 granulepos);
+  PRBool Init();
+  PRBool IsHeader(ogg_packet* aPacket);
+  nsresult PageIn(ogg_page* aPage); 
 
   // Returns the maximum number of microseconds which a keyframe can be offset
   // from any given interframe.
   PRInt64 MaxKeyframeOffset();
 
   // Returns the end time that a granulepos represents.
   static PRInt64 Time(th_info* aInfo, PRInt64 aGranulePos); 
-  
+
   th_info mInfo;
   th_comment mComment;
   th_setup_info *mSetup;
   th_dec_ctx* mCtx;
 
   float mPixelAspectRatio;
+
+private:
+
+  // Reconstructs the granulepos of Theora packets stored in the
+  // mUnstamped array. mUnstamped must be filled with consecutive packets from
+  // the stream, with the last packet having a known granulepos. Using this
+  // known granulepos, and the known frame numbers, we recover the granulepos
+  // of all frames in the array. This enables us to determine their timestamps.
+  void ReconstructTheoraGranulepos();
+
 };
 
 // Constructs a 32bit version number out of two 16 bit major,minor
 // version numbers.
 #define SKELETON_VERSION(major, minor) (((major)<<16)|(minor))
 
 class nsSkeletonState : public nsOggCodecState {
 public:
   nsSkeletonState(ogg_page* aBosPage);
-  virtual ~nsSkeletonState();
-  virtual CodecType GetType() { return TYPE_SKELETON; }
-  virtual PRBool DecodeHeader(ogg_packet* aPacket);
-  virtual PRInt64 Time(PRInt64 granulepos) { return -1; }
-  virtual PRBool Init() { return PR_TRUE; }
+  ~nsSkeletonState();
+  CodecType GetType() { return TYPE_SKELETON; }
+  PRBool DecodeHeader(ogg_packet* aPacket);
+  PRInt64 Time(PRInt64 granulepos) { return -1; }
+  PRBool Init() { return PR_TRUE; }
+  PRBool IsHeader(ogg_packet* aPacket) { return PR_TRUE; }
 
   // Return PR_TRUE if the given time (in milliseconds) is within
   // the presentation time defined in the skeleton track.
   PRBool IsPresentable(PRInt64 aTime) { return aTime >= mPresentationTime; }
 
   // Stores the offset of the page on which a keyframe starts,
   // and its presentation time.
   class nsKeyPoint {
--- a/content/media/ogg/nsOggReader.cpp
+++ b/content/media/ogg/nsOggReader.cpp
@@ -93,27 +93,34 @@ PageSync(nsMediaStream* aStream,
          PRInt64 aEndOffset,
          ogg_page* aPage,
          int& aSkippedBytes);
 
 // 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;
 
+class nsAutoReleasePacket {
+public:
+  nsAutoReleasePacket(ogg_packet* aPacket) : mPacket(aPacket) { }
+  ~nsAutoReleasePacket() {
+    nsOggCodecState::ReleasePacket(mPacket);
+  }
+private:
+  ogg_packet* mPacket;
+};
+
 nsOggReader::nsOggReader(nsBuiltinDecoder* aDecoder)
   : nsBuiltinDecoderReader(aDecoder),
     mTheoraState(nsnull),
     mVorbisState(nsnull),
     mSkeletonState(nsnull),
     mVorbisSerial(0),
     mTheoraSerial(0),
-    mPageOffset(0),
-    mTheoraGranulepos(-1),
-    mVorbisGranulepos(-1),
-    mDataOffset(0)
+    mPageOffset(0)
 {
   MOZ_COUNT_CTOR(nsOggReader);
 }
 
 nsOggReader::~nsOggReader()
 {
   ogg_sync_clear(&mOggState);
   MOZ_COUNT_DTOR(nsOggReader);
@@ -129,21 +136,16 @@ nsresult nsOggReader::Init(nsBuiltinDeco
   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;
   }
 
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
     // Discard any previously buffered packets/pages.
@@ -154,67 +156,60 @@ nsresult nsOggReader::ResetDecode()
     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;
+PRBool nsOggReader::ReadHeaders(nsOggCodecState* aState)
+{
+  while (!aState->DoneReadingHeaders()) {
+    ogg_packet* packet = NextOggPacket(aState);
+    nsAutoReleasePacket autoRelease(packet);
+    if (!packet || !aState->IsHeader(packet)) {
+      aState->Deactivate();
+    } else {
+      aState->DecodeHeader(packet);
     }
   }
-  return PR_TRUE;
+  return aState->Init();
 }
 
 nsresult nsOggReader::ReadMetadata(nsVideoInfo* aInfo)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on play state machine thread.");
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
   // We read packets until all bitstreams have read all their header packets.
   // We record the offset of the first non-header page so that we know
   // what page to seek to when seeking to the media start.
 
   ogg_page page;
-  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);
+  while (!readAllBOS) {
+    PRInt64 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);
 
 #ifdef DEBUG
       PRBool r =
 #endif
-        mCodecStates.Put(serial, codecState);
+      mCodecStates.Put(serial, codecState);
       NS_ASSERTION(r, "Failed to insert into mCodecStates");
       bitstreams.AppendElement(codecState);
       mKnownStreams.AppendElement(serial);
       if (codecState &&
           codecState->GetType() == nsOggCodecState::TYPE_VORBIS &&
           !mVorbisState)
       {
         // First Vorbis bitstream, we'll play this one. Subsequent Vorbis
@@ -240,286 +235,177 @@ nsresult nsOggReader::ReadMetadata(nsVid
       // 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;
+    if (NS_FAILED(codecState->PageIn(&page))) {
+      return NS_ERROR_FAILURE;
     }
   }
+
+  // We've read all BOS pages, so we know the streams contained in the media.
+  // Now process all available header packets in the active Theora, Vorbis and
+  // Skeleton streams.
+
   // Deactivate any non-primary bitstreams.
   for (PRUint32 i = 0; i < bitstreams.Length(); i++) {
     nsOggCodecState* s = bitstreams[i];
     if (s != mVorbisState && s != mTheoraState && s != mSkeletonState) {
       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.
-  // Set a default callback period for if we have no video data
-  if (mTheoraState && mTheoraState->Init()) {
-    gfxIntSize sz(mTheoraState->mInfo.pic_width,
-                  mTheoraState->mInfo.pic_height);
-    mDecoder->SetVideoData(sz, mTheoraState->mPixelAspectRatio, nsnull, TimeStamp::Now());
-  }
-  if (mVorbisState) {
-    mVorbisState->Init();
-  }
-
-  if (!HasAudio() && !HasVideo() && mSkeletonState) {
-    // We have a skeleton track, but no audio or video, may as well disable
-    // the skeleton, we can't do anything useful with this media.
-    mSkeletonState->Deactivate();
-  }
-
-  mInfo.mHasAudio = HasAudio();
-  mInfo.mHasVideo = HasVideo();
-  if (HasAudio()) {
-    mInfo.mAudioRate = mVorbisState->mInfo.rate;
-    mInfo.mAudioChannels = mVorbisState->mInfo.channels;
-  }
-  if (HasVideo()) {
+  if (mTheoraState && ReadHeaders(mTheoraState)) {
+    mInfo.mHasVideo = PR_TRUE;
     mInfo.mPixelAspectRatio = mTheoraState->mPixelAspectRatio;
     mInfo.mPicture = nsIntRect(mTheoraState->mInfo.pic_x,
-                               mTheoraState->mInfo.pic_y,
-                               mTheoraState->mInfo.pic_width,
-                               mTheoraState->mInfo.pic_height);
+                                mTheoraState->mInfo.pic_y,
+                                mTheoraState->mInfo.pic_width,
+                                mTheoraState->mInfo.pic_height);
     mInfo.mFrame = nsIntSize(mTheoraState->mInfo.frame_width,
-                             mTheoraState->mInfo.frame_height);
+                              mTheoraState->mInfo.frame_height);
     mInfo.mDisplay = nsIntSize(mInfo.mPicture.width,
-                               mInfo.mPicture.height);
+                                mInfo.mPicture.height);
+    gfxIntSize sz(mTheoraState->mInfo.pic_width,
+                  mTheoraState->mInfo.pic_height);
+    mDecoder->SetVideoData(sz,
+                           mTheoraState->mPixelAspectRatio,
+                           nsnull,
+                           TimeStamp::Now());
+    // Copy Theora info data for time computations on other threads.
+    memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo));
+    mTheoraSerial = mTheoraState->mSerial;
+  } else {
+    memset(&mTheoraInfo, 0, sizeof(mTheoraInfo));
   }
 
-  if (mSkeletonState && mSkeletonState->HasIndex()) {
-    // Extract the duration info out of the index, so we don't need to seek to
-    // the end of stream to get it.
-    nsAutoTArray<PRUint32, 2> tracks;
-    if (HasVideo()) {
-      tracks.AppendElement(mTheoraState->mSerial);
-    }
-    if (HasAudio()) {
-      tracks.AppendElement(mVorbisState->mSerial);
-    }
-    PRInt64 duration = 0;
-    if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
-      ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
-      ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
-      mDecoder->GetStateMachine()->SetDuration(duration);
-      LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration));
-    }
-  }
-
-  // Copy Vorbis and Theora info data for time computations on other threads.
-  if (mVorbisState) {
+  if (mVorbisState && ReadHeaders(mVorbisState)) {
+    mInfo.mHasAudio = PR_TRUE;
+    mInfo.mAudioRate = mVorbisState->mInfo.rate;
+    mInfo.mAudioChannels = mVorbisState->mInfo.channels;
+    // Copy Vorbis info data for time computations on other threads.
     memcpy(&mVorbisInfo, &mVorbisState->mInfo, sizeof(mVorbisInfo));
     mVorbisInfo.codec_setup = NULL;
     mVorbisSerial = mVorbisState->mSerial;
+  } else {
+    memset(&mVorbisInfo, 0, sizeof(mVorbisInfo));
   }
 
-  if (mTheoraState) {
-    memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo));
-    mTheoraSerial = mTheoraState->mSerial;
+  if (mSkeletonState) {
+    if (!HasAudio() && !HasVideo()) {
+      // We have a skeleton track, but no audio or video, may as well disable
+      // the skeleton, we can't do anything useful with this media.
+      mSkeletonState->Deactivate();
+    } else if (ReadHeaders(mSkeletonState) && mSkeletonState->HasIndex()) {
+      // Extract the duration info out of the index, so we don't need to seek to
+      // the end of stream to get it.
+      nsAutoTArray<PRUint32, 2> tracks;
+      if (HasVideo()) {
+        tracks.AppendElement(mTheoraState->mSerial);
+      }
+      if (HasAudio()) {
+        tracks.AppendElement(mVorbisState->mSerial);
+      }
+      PRInt64 duration = 0;
+      if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
+        ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
+        ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
+        mDecoder->GetStateMachine()->SetDuration(duration);
+        LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration));
+      }
+    }
   }
 
   *aInfo = mInfo;
 
-  LOG(PR_LOG_DEBUG, ("Done loading headers, data offset %lld", mDataOffset));
-
   return NS_OK;
 }
 
-nsresult nsOggReader::DecodeVorbis(nsTArray<nsAutoPtr<SoundData> >& aChunks,
-                                   ogg_packet* aPacket)
-{
-  // Successfully read a packet.
+nsresult nsOggReader::DecodeVorbis(ogg_packet* aPacket) {
+  NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!");
+
   if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
     return NS_ERROR_FAILURE;
   }
   if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
                                &mVorbisState->mBlock) != 0)
   {
     return NS_ERROR_FAILURE;
   }
 
   VorbisPCMValue** pcm = 0;
   PRInt32 samples = 0;
   PRUint32 channels = mVorbisState->mInfo.channels;
+  ogg_int64_t endSample = aPacket->granulepos;
   while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
+    mVorbisState->ValidateVorbisPacketSamples(aPacket, samples);
     SoundDataValue* buffer = new SoundDataValue[samples * channels];
     for (PRUint32 j = 0; j < channels; ++j) {
       VorbisPCMValue* channel = pcm[j];
       for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
         buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]);
       }
     }
 
     PRInt64 duration = mVorbisState->Time((PRInt64)samples);
-    PRInt64 startTime = mVorbisState->Time(mVorbisGranulepos);
+    PRInt64 startTime = mVorbisState->Time(endSample - samples);
     SoundData* s = new SoundData(mPageOffset,
                                  startTime,
                                  duration,
                                  samples,
                                  buffer,
                                  channels);
-    if (mVorbisGranulepos != -1) {
-      mVorbisGranulepos += samples;
-    }
-    if (!aChunks.AppendElement(s)) {
-      delete s;
-    }
+    mAudioQueue.Push(s);
+    endSample -= samples;
     if (vorbis_synthesis_read(&mVorbisState->mDsp, samples) != 0) {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
 PRBool nsOggReader::DecodeAudioData()
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
                "Should be on playback or decode thread.");
   NS_ASSERTION(mVorbisState!=0, "Need Vorbis state to decode audio");
-  ogg_packet packet;
-  packet.granulepos = -1;
 
-  PRBool endOfStream = PR_FALSE;
-
-  nsAutoTArray<nsAutoPtr<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");
-      }
+  // Read the next data packet. Skip any non-data packets we encounter.
+  ogg_packet* packet = 0;
+  do {
+    if (packet) {
+      nsOggCodecState::ReleasePacket(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;
-      }
-    }
+    packet = NextOggPacket(mVorbisState);
+  } while (packet && mVorbisState->IsHeader(packet));
+  if (!packet) {
+    mAudioQueue.Finish();
+    return PR_FALSE;
   }
 
-  // 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].forget());
-  }
-
-  if (endOfStream) {
+  NS_ASSERTION(packet && packet->granulepos != -1,
+    "Must have packet with known granulepos");
+  nsAutoReleasePacket autoRelease(packet);
+  DecodeVorbis(packet);
+  if (packet->e_o_s) {
     // 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<nsAutoPtr<VideoData> >& aFrames)
 {
   PRInt64 prevTime = -1;
   PRInt64 prevGranulepos = -1;
@@ -531,33 +417,42 @@ AllFrameTimesIncrease(nsTArray<nsAutoPtr
     prevTime = f->mTime;
     prevGranulepos = f->mTimecode;
   }
 
   return PR_TRUE;
 }
 #endif
 
-nsresult nsOggReader::DecodeTheora(nsTArray<nsAutoPtr<VideoData> >& aFrames,
-                                   ogg_packet* aPacket)
+nsresult nsOggReader::DecodeTheora(ogg_packet* aPacket)
 {
+  NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1),
+    "Packets must have valid granulepos and packetno");
+
   int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
   if (ret != 0 && ret != TH_DUPFRAME) {
     return NS_ERROR_FAILURE;
   }
   PRInt64 time = mTheoraState->StartTime(aPacket->granulepos);
+
+  // Don't use the frame if it's outside the bounds of the presentation
+  // start time in the skeleton track. Note we still must submit the frame
+  // to the decoder (via th_decode_packetin), as the frames which are
+  // presentable may depend on this frame's data.
+  if (mSkeletonState && !mSkeletonState->IsPresentable(time)) {
+    return NS_OK;
+  }
+
   PRInt64 endTime = mTheoraState->Time(aPacket->granulepos);
   if (ret == TH_DUPFRAME) {
     VideoData* v = VideoData::CreateDuplicate(mPageOffset,
                                               time,
                                               endTime,
                                               aPacket->granulepos);
-    if (!aFrames.AppendElement(v)) {
-      delete v;
-    }
+    mVideoQueue.Push(v);
   } 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;
@@ -578,228 +473,71 @@ nsresult nsOggReader::DecodeTheora(nsTAr
                                      isKeyframe,
                                      aPacket->granulepos);
     if (!v) {
       // There may be other reasons for this error, but for
       // simplicity just assume the worst case: out of memory.
       NS_WARNING("Failed to allocate memory for video frame");
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    if (!aFrames.AppendElement(v)) {
-      delete v;
-    }
+    mVideoQueue.Push(v);
   }
   return NS_OK;
 }
 
 PRBool nsOggReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
                                      PRInt64 aTimeThreshold)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
                "Should be on state machine or AV thread.");
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   PRUint32 parsed = 0, decoded = 0;
   nsMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
-  // 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<nsAutoPtr<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;
-      }
-      parsed++;
-
-      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) {
-      // Reconstruct the granulepos (and thus timestamps) of the decoded
-      // frames. Granulepos are stored as ((keyframe<<shift)+offset). We
-      // know the granulepos of the last frame in the list, so we can infer
-      // the granulepos of the intermediate frames using their frame numbers.
-      ogg_int64_t shift = mTheoraState->mInfo.keyframe_granule_shift;
-      ogg_int64_t version_3_2_1 = TheoraVersion(&mTheoraState->mInfo,3,2,1);
-      ogg_int64_t lastFrame = th_granule_frame(mTheoraState->mCtx,
-                                               packet.granulepos) + version_3_2_1;
-      ogg_int64_t firstFrame = lastFrame - frames.Length() + 1;
-
-      // Until we encounter a keyframe, we'll assume that the "keyframe"
-      // segment of the granulepos is the first frame, or if that causes
-      // the "offset" segment to overflow, we assume the required
-      // keyframe is maximumally offset. Until we encounter a keyframe
-      // the granulepos will probably be wrong, but we can't decode the
-      // frame anyway (since we don't have its keyframe) so it doesn't really
-      // matter.
-      ogg_int64_t keyframe = packet.granulepos >> shift;
+  // Read the next data packet. Skip any non-data packets we encounter.
+  ogg_packet* packet = 0;
+  do {
+    if (packet) {
+      nsOggCodecState::ReleasePacket(packet);
+    }
+    packet = NextOggPacket(mTheoraState);
+  } while (packet && mTheoraState->IsHeader(packet));
+  if (!packet) {
+    mVideoQueue.Finish();
+    return PR_FALSE;
+  }
+  nsAutoReleasePacket autoRelease(packet);
 
-      // The lastFrame, firstFrame, keyframe variables, as well as the frame
-      // variable in the loop below, store the frame number for Theora
-      // version >= 3.2.1 streams, and store the frame index for Theora
-      // version < 3.2.1 streams.
-      for (PRUint32 i = 0; i < frames.Length() - 1; ++i) {
-        ogg_int64_t frame = firstFrame + i;
-        ogg_int64_t granulepos;
-        if (frames[i]->mKeyframe) {
-          granulepos = frame << shift;
-          keyframe = frame;
-        } else if (frame >= keyframe &&
-                   frame - keyframe < ((ogg_int64_t)1 << shift))
-        {
-          // (frame - keyframe) won't overflow the "offset" segment of the
-          // granulepos, so it's safe to calculate the granulepos.
-          granulepos = (keyframe << shift) + (frame - keyframe);
-        } else {
-          // (frame - keyframeno) will overflow the "offset" segment of the
-          // granulepos, so we take "keyframe" to be the max possible offset
-          // frame instead.
-          ogg_int64_t k = NS_MAX(frame - (((ogg_int64_t)1 << shift) - 1), version_3_2_1);
-          granulepos = (k << shift) + (frame - k);
-        }
-        // 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(granulepos >= version_3_2_1,
-                     "Invalid granulepos for Theora version");
-
-        // Check that the frame's granule number is one more than the
-        // previous frame's.
-        NS_ASSERTION(i == 0 ||
-                     th_granule_frame(mTheoraState->mCtx, granulepos) ==
-                     th_granule_frame(mTheoraState->mCtx, frames[i-1]->mTimecode) + 1,
-                     "Granulepos calculation is incorrect!");
-
-        frames[i]->mTime = mTheoraState->StartTime(granulepos);
-        frames[i]->mEndTime = mTheoraState->Time(granulepos);
-        NS_ASSERTION(frames[i]->mEndTime >= frames[i]->mTime, "Frame must start before it ends.");
-        frames[i]->mTimecode = granulepos;
-      }
-      NS_ASSERTION(AllFrameTimesIncrease(frames), "All frames must have granulepos");
-
-      // Check that the second to last frame's granule number is one less than
-      // the last frame's (the known granule number). If not our granulepos
-      // recovery missed a beat.
-      NS_ASSERTION(frames.Length() < 2 ||
-        th_granule_frame(mTheoraState->mCtx, frames[frames.Length()-2]->mTimecode) + 1 ==
-        th_granule_frame(mTheoraState->mCtx, packet.granulepos),
-        "Granulepos recovery should catch up with packet.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();
+  parsed++;
+  NS_ASSERTION(packet && packet->granulepos != -1,
+                "Must know first packet's granulepos");
+  PRBool eos = packet->e_o_s;
+  PRInt64 frameEndTime = mTheoraState->Time(packet->granulepos);
+  if (!aKeyframeSkip ||
+     (th_packet_iskeyframe(packet) && frameEndTime >= aTimeThreshold))
+  {
+    aKeyframeSkip = PR_FALSE;
+    nsresult res = DecodeTheora(packet);
+    decoded++;
+    if (NS_FAILED(res)) {
       return PR_FALSE;
     }
-    parsed++;
-
-    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].forget());
-    // Don't use the frame if it's outside the bounds of the presentation
-    // start time in the skeleton track.
-    if (!mSkeletonState || mSkeletonState->IsPresentable(data->mTime)) {
-      if (aKeyframeSkip && data->mKeyframe) {
-        aKeyframeSkip = PR_FALSE;
-      }
- 
-      if (!aKeyframeSkip && data->mEndTime >= aTimeThreshold) {
-        mVideoQueue.Push(data.forget());
-        decoded++;
-      }
-    }
-  }
-
-  if (endOfStream) {
+  if (eos) {
     // We've encountered an end of bitstream packet. Inform the queue that
     // there will be no more frames.
     mVideoQueue.Finish();
+    return PR_FALSE;
   }
 
-  return !endOfStream;
+  return PR_TRUE;
 }
 
 PRInt64 nsOggReader::ReadOggPage(ogg_page* aPage)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
                "Should be on play state machine or decode thread.");
   mReentrantMonitor.AssertCurrentThreadIn();
 
@@ -832,61 +570,44 @@ PRInt64 nsOggReader::ReadOggPage(ogg_pag
     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)
+ogg_packet* nsOggReader::NextOggPacket(nsOggCodecState* aCodecState)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
                "Should be on play state machine or decode thread.");
   mReentrantMonitor.AssertCurrentThreadIn();
 
   if (!aCodecState || !aCodecState->mActive) {
-    return PR_FALSE;
+    return nsnull;
   }
 
-  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;
-    }
-
+  ogg_packet* packet;
+  while ((packet = aCodecState->PacketOut()) == nsnull) {
     // The codec state does not have any buffered pages, so try to read another
     // page from the channel.
+    ogg_page page;
     if (ReadOggPage(&page) == -1) {
-      return PR_FALSE;
+      return nsnull;
     }
 
     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);
+    if (codecState && NS_FAILED(codecState->PageIn(&page))) {
+      return nsnull;
     }
   }
 
-  return PR_TRUE;
+  return packet;
 }
 
 // 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;
@@ -901,38 +622,31 @@ GetChecksum(ogg_page* page)
 
 VideoData* nsOggReader::FindStartTime(PRInt64 aOffset,
                                       PRInt64& aOutStartTime)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, nsnull);
-  // Ensure aOffset is after mDataOffset, the offset of the first non-header page.
-  // This prevents us from trying to parse header pages as content pages.
-  NS_ASSERTION(mDataOffset > 0, "Must know mDataOffset by now");
-  PRInt64 offset = NS_MAX(mDataOffset, aOffset);
-  nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+  nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   NS_ENSURE_SUCCESS(res, nsnull);
-  return nsBuiltinDecoderReader::FindStartTime(offset, aOutStartTime);
+  return nsBuiltinDecoderReader::FindStartTime(aOffset, aOutStartTime);
 }
 
 PRInt64 nsOggReader::FindEndTime(PRInt64 aEndOffset)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
-  NS_ASSERTION(mDataOffset > 0,
-               "Should have offset of first non-header page");
-  PRInt64 offset = NS_MAX(mDataOffset, aEndOffset);
-  PRInt64 endTime = FindEndTime(mDataOffset, offset, PR_FALSE, &mOggState);
+  PRInt64 endTime = FindEndTime(0, aEndOffset, PR_FALSE, &mOggState);
   // Reset read head to start of media data.
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, -1);
-  nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
+  nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
   NS_ENSURE_SUCCESS(res, -1);
   return endTime;
 }
 
 PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
                                  PRInt64 aEndOffset,
                                  PRBool aCachedDataOnly,
                                  ogg_sync_state* aState)
@@ -1063,20 +777,18 @@ nsresult nsOggReader::GetSeekRanges(nsTA
 
   for (PRUint32 index = 0; index < cached.Length(); index++) {
     nsByteRange& range = cached[index];
     PRInt64 startTime = -1;
     PRInt64 endTime = -1;
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
-    // Ensure the offsets are after the header pages.
-    PRInt64 startOffset = NS_MAX(cached[index].mStart, mDataOffset);
-    PRInt64 endOffset = NS_MAX(cached[index].mEnd, mDataOffset);
-
+    PRInt64 startOffset = range.mStart;
+    PRInt64 endOffset = range.mEnd;
     FindStartTime(startOffset, startTime);
     if (startTime != -1 &&
         ((endTime = FindEndTime(endOffset)) != -1))
     {
       NS_ASSERTION(startTime < endTime,
                    "Start time must be before end time");
       aRanges.AppendElement(SeekRange(startOffset,
                                       endOffset,
@@ -1094,17 +806,17 @@ nsOggReader::SeekRange
 nsOggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
                              PRInt64 aTarget,
                              PRInt64 aStartTime,
                              PRInt64 aEndTime,
                              PRBool aExact)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
-  PRInt64 so = mDataOffset;
+  PRInt64 so = 0;
   PRInt64 eo = mDecoder->GetCurrentStream()->GetLength();
   PRInt64 st = aStartTime;
   PRInt64 et = aEndTime;
   for (PRUint32 i = 0; i < ranges.Length(); i++) {
     const SeekRange &r = ranges[i];
     if (r.mTimeStart < aTarget) {
       so = r.mOffsetStart;
       st = r.mTimeStart;
@@ -1256,18 +968,16 @@ nsresult nsOggReader::SeekInBufferedRang
     SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it",
                             video->mTime, keyframeTime));
     SeekRange k = SelectSeekRange(aRanges,
                                   keyframeTime,
                                   aStartTime,
                                   aEndTime,
                                   PR_FALSE);
     res = SeekBisection(keyframeTime, k, SEEK_FUZZ_USECS);
-    NS_ASSERTION(mTheoraGranulepos == -1, "SeekBisection must reset Theora decode");
-    NS_ASSERTION(mVorbisGranulepos == -1, "SeekBisection must reset Vorbis decode");
   }
   return res;
 }
 
 PRBool nsOggReader::CanDecodeToTarget(PRInt64 aTarget,
                                       PRInt64 aCurrentTime)
 {
   // We can decode to the target if the target is no further than the
@@ -1300,20 +1010,17 @@ nsresult nsOggReader::SeekInUnbuffered(P
   PRInt64 keyframeOffsetMs = 0;
   if (HasVideo() && mTheoraState) {
     keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
   }
   PRInt64 seekTarget = NS_MAX(aStartTime, aTarget - keyframeOffsetMs);
   // Minimize the bisection search space using the known timestamps from the
   // buffered ranges.
   SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, PR_FALSE);
-  nsresult res = SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
-  NS_ASSERTION(mTheoraGranulepos == -1, "SeekBisection must reset Theora decode");
-  NS_ASSERTION(mVorbisGranulepos == -1, "SeekBisection must reset Vorbis decode");
-  return res;
+  return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
 }
 
 nsresult nsOggReader::Seek(PRInt64 aTarget,
                            PRInt64 aStartTime,
                            PRInt64 aEndTime,
                            PRInt64 aCurrentTime)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
@@ -1322,20 +1029,20 @@ nsresult nsOggReader::Seek(PRInt64 aTarg
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
   nsresult res;
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream != nsnull, NS_ERROR_FAILURE);
 
   if (aTarget == aStartTime) {
     // We've seeked to the media start. Just seek to the offset of the first
     // content page.
-    res = stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
+    res = stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
 
-    mPageOffset = mDataOffset;
+    mPageOffset = 0;
     res = ResetDecode();
     NS_ENSURE_SUCCESS(res,res);
 
     NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
     {
       ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       mDecoder->UpdatePlaybackPosition(aStartTime);
@@ -1451,19 +1158,19 @@ nsresult nsOggReader::SeekBisection(PRIn
                "Should be on state machine thread.");
   nsresult res;
   nsMediaStream* stream = mDecoder->GetCurrentStream();
 
   if (aTarget == aRange.mTimeStart) {
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
-    res = stream->Seek(nsISeekableStream::NS_SEEK_SET, mDataOffset);
+    res = stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
-    mPageOffset = mDataOffset;
+    mPageOffset = 0;
     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; // Length of the page at startOffset.
@@ -1715,24 +1422,24 @@ nsresult nsOggReader::GetBuffered(nsTime
   // they contain. nsMediaStream::GetNextCachedData(offset) returns -1 when
   // offset is after the end of the media stream, or there's no more cached
   // data after the offset. This loop will run until we've checked every
   // buffered range in the media, in increasing order of offset.
   ogg_sync_state state;
   ogg_sync_init(&state);
   for (PRUint32 index = 0; index < ranges.Length(); index++) {
     // Ensure the offsets are after the header pages.
-    PRInt64 startOffset = NS_MAX(ranges[index].mStart, mDataOffset);
-    PRInt64 endOffset = NS_MAX(ranges[index].mEnd, mDataOffset);
+    PRInt64 startOffset = ranges[index].mStart;
+    PRInt64 endOffset = ranges[index].mEnd;
 
     // Because the granulepos time is actually the end time of the page,
-    // we special-case (startOffset == mDataOffset) so that the first
+    // we special-case (startOffset == 0) so that the first
     // buffered range always appears to be buffered from the media start
     // time, rather than from the end-time of the first page.
-    PRInt64 startTime = (startOffset == mDataOffset) ? aStartTime : -1;
+    PRInt64 startTime = (startOffset == 0) ? aStartTime : -1;
 
     // Find the start time of the range. Read pages until we find one with a
     // granulepos which we can convert into a timestamp to use as the time of
     // the start of the buffered range.
     ogg_sync_reset(&state);
     while (startTime == -1) {
       ogg_page page;
       PRInt32 discard;
--- a/content/media/ogg/nsOggReader.h
+++ b/content/media/ogg/nsOggReader.h
@@ -184,34 +184,16 @@ private:
   // regular blocking reads from the media stream. If PRBool aCachedDataOnly
   // is PR_TRUE, and aState is not mOggState, this can safely be called on
   // the main thread, otherwise it must be called on the state machine thread.
   PRInt64 FindEndTime(PRInt64 aStartOffset,
                       PRInt64 aEndOffset,
                       PRBool aCachedDataOnly,
                       ogg_sync_state* aState);
 
-  // Decodes one packet of Vorbis data, storing the resulting chunks of
-  // PCM samples in aChunks.
-  nsresult DecodeVorbis(nsTArray<nsAutoPtr<SoundData> >& aChunks,
-                        ogg_packet* aPacket);
-
-  // May return NS_ERROR_OUT_OF_MEMORY. Caller must have obtained the
-  // reader's monitor.
-  nsresult DecodeTheora(nsTArray<nsAutoPtr<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 usecs on both the
   // Theora and Vorbis bitstreams. Limits its search to data inside aRange;
   // i.e. it will only read inside of the aRange's start and end offsets.
   // aFuzz is the number of usecs of leniency we'll allow; we'll terminate the
   // seek when we land in the range (aTime - aFuzz, aTime) usecs.
   nsresult SeekBisection(PRInt64 aTarget,
                          const SeekRange& aRange,
@@ -236,16 +218,41 @@ private:
   // and byte offsets known in aRanges. We can then use this to minimize our
   // bisection's search space when the target isn't in a known buffered range.
   SeekRange SelectSeekRange(const nsTArray<SeekRange>& aRanges,
                             PRInt64 aTarget,
                             PRInt64 aStartTime,
                             PRInt64 aEndTime,
                             PRBool aExact);
 private:
+
+  // Decodes a packet of Vorbis data, and inserts its samples into the 
+  // audio queue.
+  nsresult DecodeVorbis(ogg_packet* aPacket);
+
+  // Decodes a packet of Theora data, and inserts its frame into the
+  // video queue. May return NS_ERROR_OUT_OF_MEMORY. Caller must have obtained
+  // the reader's monitor.
+  nsresult DecodeTheora(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);
+
+  // Reads and decodes header packets for aState, until either header decode
+  // fails, or is complete. Initializes the codec state before returning.
+  // Returns PR_TRUE if reading headers and initializtion of the stream
+  // succeeds.
+  PRBool ReadHeaders(nsOggCodecState* aState);
+
+  // Returns the next Ogg packet for an bitstream/codec state. Returns a
+  // pointer to an ogg_packet on success, or nsnull if the read failed.
+  // The caller is responsible for deleting the packet and its |packet| field.
+  ogg_packet* NextOggPacket(nsOggCodecState* aCodecState);
+
   // Maps Ogg serialnos to nsOggStreams.
   nsClassHashtable<nsUint32HashKey, nsOggCodecState> mCodecStates;
 
   // Array of serial numbers of streams that were encountered during
   // initial metadata load. Written on state machine thread during
   // metadata loading and read on the main thread only after metadata
   // is loaded.
   nsAutoTArray<PRUint32,4> mKnownStreams;
@@ -271,22 +278,11 @@ private:
   PRUint32 mVorbisSerial;
   PRUint32 mTheoraSerial;
   vorbis_info mVorbisInfo;
   th_info mTheoraInfo;
 
   // 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;
-
-  // The granulepos of the last decoded Theora frame.
-  PRInt64 mTheoraGranulepos;
-
-  // The granulepos of the last decoded Vorbis sample.
-  PRInt64 mVorbisGranulepos;
-
-  // The offset of the first non-header page in the file, in bytes.
-  // Used to seek to the start of the media, and to prevent us trying to
-  // decode pages before this offset (the header pages) as content pages.
-  PRInt64 mDataOffset;
 };
 
 #endif
--- a/content/media/raw/nsRawReader.cpp
+++ b/content/media/raw/nsRawReader.cpp
@@ -99,17 +99,16 @@ nsresult nsRawReader::ReadMetadata(nsVid
   mInfo.mPicture.height = mMetadata.frameHeight;
   mInfo.mFrame.width = mMetadata.frameWidth;
   mInfo.mFrame.height = mMetadata.frameHeight;
   if (mMetadata.aspectDenominator == 0 ||
       mMetadata.framerateDenominator == 0)
     return NS_ERROR_FAILURE; // Invalid data
   mInfo.mPixelAspectRatio = static_cast<float>(mMetadata.aspectNumerator) / 
                             mMetadata.aspectDenominator;
-  mInfo.mDataOffset = sizeof(nsRawVideoHeader) + 1;
   mInfo.mHasAudio = PR_FALSE;
 
   mFrameRate = static_cast<float>(mMetadata.framerateNumerator) /
                mMetadata.framerateDenominator;
 
   // Make some sanity checks
   if (mFrameRate > 45 ||
       mFrameRate == 0 ||