Bug 598217 - Share nsWebMBufferedState between cloned decoders. r=roc a=blocking2.0
authorMatthew Gregan <kinetik@flim.org>
Tue, 21 Sep 2010 12:49:50 +1200
changeset 54993 bc87d90b82dcf580bfeb0ae5a432d4facea43a45
parent 54992 0393eb812dded7ac1483d2588c16c82a0346fc27
child 54994 5f0b56b5b3b34862b1f7cc4c2451b096dca4cc9a
push id16110
push usercpearce@mozilla.com
push dateWed, 06 Oct 2010 23:45:41 +0000
treeherdermozilla-central@2ffbe81d752f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, blocking2.0
bugs598217
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 598217 - Share nsWebMBufferedState between cloned decoders. r=roc a=blocking2.0
content/html/content/src/nsHTMLMediaElement.cpp
content/media/nsBuiltinDecoder.cpp
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderReader.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/nsMediaDecoder.h
content/media/ogg/nsOggReader.cpp
content/media/ogg/nsOggReader.h
content/media/raw/nsRawReader.cpp
content/media/raw/nsRawReader.h
content/media/wave/nsWaveDecoder.cpp
content/media/wave/nsWaveDecoder.h
content/media/webm/nsWebMBufferedParser.cpp
content/media/webm/nsWebMBufferedParser.h
content/media/webm/nsWebMReader.cpp
content/media/webm/nsWebMReader.h
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1824,17 +1824,17 @@ nsresult nsHTMLMediaElement::InitializeD
 
   nsMediaStream* stream = originalStream->CloneData(decoder);
   if (!stream) {
     return NS_ERROR_FAILURE;
   }
 
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
 
-  nsresult rv = decoder->Load(stream, nsnull);
+  nsresult rv = decoder->Load(stream, nsnull, aOriginal);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return FinishDecoderSetup(decoder);
 }
 
 nsresult nsHTMLMediaElement::InitializeDecoderForChannel(nsIChannel *aChannel,
@@ -1851,17 +1851,17 @@ nsresult nsHTMLMediaElement::InitializeD
   LOG(PR_LOG_DEBUG, ("%p Created decoder %p for type %s", this, decoder.get(), mimeType.get()));
 
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
 
   nsMediaStream* stream = nsMediaStream::Create(decoder, aChannel);
   if (!stream)
     return NS_ERROR_OUT_OF_MEMORY;
 
-  nsresult rv = decoder->Load(stream, aListener);
+  nsresult rv = decoder->Load(stream, aListener, nsnull);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Decoder successfully created, the decoder now owns the nsMediaStream
   // which owns the channel.
   mChannel = nsnull;
 
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -180,17 +180,18 @@ void nsBuiltinDecoder::Shutdown()
 nsBuiltinDecoder::~nsBuiltinDecoder()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   UnpinForSeek();
   MOZ_COUNT_DTOR(nsBuiltinDecoder);
 }
 
 nsresult nsBuiltinDecoder::Load(nsMediaStream* aStream,
-                            nsIStreamListener** aStreamListener)
+                            nsIStreamListener** aStreamListener,
+                            nsMediaDecoder* aCloneDonor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
   {
     // Hold the lock while we do this to set proper lock ordering
@@ -207,17 +208,19 @@ nsresult nsBuiltinDecoder::Load(nsMediaS
     mStream = aStream;
   }
 
   mDecoderStateMachine = CreateStateMachine();
   if (!mDecoderStateMachine) {
     return NS_ERROR_FAILURE;
   }
 
-  if (NS_FAILED(mDecoderStateMachine->Init())) {
+  nsBuiltinDecoder* cloneDonor = static_cast<nsBuiltinDecoder*>(aCloneDonor);
+  if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ?
+                                           cloneDonor->mDecoderStateMachine : nsnull))) {
     return NS_ERROR_FAILURE;
   }
   {
     MonitorAutoEnter mon(mMonitor);
     mDecoderStateMachine->SetSeekable(mSeekable);
     mDecoderStateMachine->SetDuration(mDuration);
   }
 
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -232,17 +232,17 @@ public:
     DECODER_STATE_SEEKING,
     DECODER_STATE_BUFFERING,
     DECODER_STATE_COMPLETED,
     DECODER_STATE_SHUTDOWN
   };
 
   // Initializes the state machine, returns NS_OK on success, or
   // NS_ERROR_FAILURE on failure.
-  virtual nsresult Init() = 0;
+  virtual nsresult Init(nsDecoderStateMachine* aCloneDonor) = 0;
 
   // Return the current decode state. The decoder monitor must be
   // obtained before calling this.
   virtual State GetState() = 0;
 
   // Set the audio volume. The decoder monitor must be obtained before
   // calling this.
   virtual void SetVolume(float aVolume) = 0;
@@ -334,17 +334,18 @@ class nsBuiltinDecoder : public nsMediaD
 
   // This method must be called by the owning object before that
   // object disposes of this decoder object.
   virtual void Shutdown();
   
   virtual float GetCurrentTime();
 
   virtual nsresult Load(nsMediaStream* aStream,
-                        nsIStreamListener** aListener);
+                        nsIStreamListener** aListener,
+                        nsMediaDecoder* aCloneDonor);
 
   virtual nsDecoderStateMachine* CreateStateMachine() = 0;
 
   // Start playback of a video. 'Load' must have previously been
   // called.
   virtual nsresult Play();
 
   // Seek to the time position in (seconds) from the start of the video.
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -406,17 +406,17 @@ class nsBuiltinDecoderReader : public ns
 public:
   typedef mozilla::Monitor Monitor;
 
   nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder);
   ~nsBuiltinDecoderReader();
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
-  virtual nsresult Init() = 0;
+  virtual nsresult Init(nsBuiltinDecoderReader* aCloneDonor) = 0;
 
   // Resets all state related to decoding, emptying all buffers etc.
   virtual nsresult ResetDecode();
 
   // Decodes an unspecified amount of audio data, enqueuing the audio data
   // in mAudioQueue. Returns PR_TRUE when there's more audio to decode,
   // PR_FALSE if the audio is finished, end of file has been reached,
   // or an un-recoverable read error has occured.
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -562,19 +562,23 @@ PRUint32 nsBuiltinDecoderStateMachine::P
   }
   if (offset != -1) {
     mDecoder->UpdatePlaybackOffset(offset);
   }
   return samples;
 }
 
 
-nsresult nsBuiltinDecoderStateMachine::Init()
+nsresult nsBuiltinDecoderStateMachine::Init(nsDecoderStateMachine* aCloneDonor)
 {
-  return mReader->Init();
+  nsBuiltinDecoderReader* cloneReader = nsnull;
+  if (aCloneDonor) {
+    cloneReader = static_cast<nsBuiltinDecoderStateMachine*>(aCloneDonor)->mReader;
+  }
+  return mReader->Init(cloneReader);
 }
 
 void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -143,17 +143,17 @@ public:
   typedef mozilla::Monitor Monitor;
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
 
   nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder, nsBuiltinDecoderReader* aReader);
   ~nsBuiltinDecoderStateMachine();
 
   // nsDecoderStateMachine interface
-  virtual nsresult Init();
+  virtual nsresult Init(nsDecoderStateMachine* aCloneDonor);
   State GetState()
   { 
     mDecoder->GetMonitor().AssertCurrentThreadIn();
     return mState; 
   }
   virtual void SetVolume(float aVolume);
   virtual void Shutdown();
   virtual PRInt64 GetDuration();
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -132,17 +132,18 @@ public:
   virtual nsresult Play() = 0;
 
   // Start downloading the media. Decode the downloaded data up to the
   // point of the first frame of data.
   // aStream is the media stream to use. Ownership of aStream passes to
   // the decoder, even if Load returns an error.
   // This is called at most once per decoder, after Init().
   virtual nsresult Load(nsMediaStream* aStream,
-                        nsIStreamListener **aListener) = 0;
+                        nsIStreamListener **aListener,
+                        nsMediaDecoder* aCloneDonor) = 0;
 
   // Called when the video file has completed downloading.
   virtual void ResourceLoaded() = 0;
 
   // Called if the media file encounters a network error.
   virtual void NetworkError() = 0;
 
   // Call from any thread safely. Return PR_TRUE if we are currently
--- a/content/media/ogg/nsOggReader.cpp
+++ b/content/media/ogg/nsOggReader.cpp
@@ -110,17 +110,17 @@ nsOggReader::nsOggReader(nsBuiltinDecode
 }
 
 nsOggReader::~nsOggReader()
 {
   ogg_sync_clear(&mOggState);
   MOZ_COUNT_DTOR(nsOggReader);
 }
 
-nsresult nsOggReader::Init() {
+nsresult nsOggReader::Init(nsBuiltinDecoderReader* aCloneDonor) {
   PRBool init = mCodecStates.Init();
   NS_ASSERTION(init, "Failed to initialize mCodecStates");
   if (!init) {
     return NS_ERROR_FAILURE;
   }
   int ret = ogg_sync_init(&mOggState);
   NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
   return NS_OK;
--- a/content/media/ogg/nsOggReader.h
+++ b/content/media/ogg/nsOggReader.h
@@ -52,17 +52,17 @@ class nsMediaDecoder;
 class nsTimeRanges;
 
 class nsOggReader : public nsBuiltinDecoderReader
 {
 public:
   nsOggReader(nsBuiltinDecoder* aDecoder);
   ~nsOggReader();
 
-  virtual nsresult Init();
+  virtual nsresult Init(nsBuiltinDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
   virtual PRBool DecodeAudioData();
 
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.  
   virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
                                   PRInt64 aTimeThreshold);
--- a/content/media/raw/nsRawReader.cpp
+++ b/content/media/raw/nsRawReader.cpp
@@ -52,17 +52,17 @@ nsRawReader::nsRawReader(nsBuiltinDecode
   MOZ_COUNT_CTOR(nsRawReader);
 }
 
 nsRawReader::~nsRawReader()
 {
   MOZ_COUNT_DTOR(nsRawReader);
 }
 
-nsresult nsRawReader::Init()
+nsresult nsRawReader::Init(nsBuiltinDecoderReader* aCloneDonor)
 {
   return NS_OK;
 }
 
 nsresult nsRawReader::ResetDecode()
 {
   mCurrentFrame = 0;
   return nsBuiltinDecoderReader::ResetDecode();
--- a/content/media/raw/nsRawReader.h
+++ b/content/media/raw/nsRawReader.h
@@ -88,17 +88,17 @@ struct nsRawVideoHeader {
 };
 
 class nsRawReader : public nsBuiltinDecoderReader
 {
 public:
   nsRawReader(nsBuiltinDecoder* aDecoder);
   ~nsRawReader();
 
-  virtual nsresult Init();
+  virtual nsresult Init(nsBuiltinDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
   virtual PRBool DecodeAudioData();
 
   virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
                                   PRInt64 aTimeThreshold);
 
   virtual PRBool HasAudio()
   {
--- a/content/media/wave/nsWaveDecoder.cpp
+++ b/content/media/wave/nsWaveDecoder.cpp
@@ -1384,17 +1384,18 @@ nsWaveDecoder::Stop()
   mPlaybackThread = nsnull;
   mPlaybackStateMachine = nsnull;
   mStream = nsnull;
 
   nsContentUtils::UnregisterShutdownObserver(this);
 }
 
 nsresult
-nsWaveDecoder::Load(nsMediaStream* aStream, nsIStreamListener** aStreamListener)
+nsWaveDecoder::Load(nsMediaStream* aStream, nsIStreamListener** aStreamListener,
+                    nsMediaDecoder* aCloneDonor)
 {
   NS_ASSERTION(aStream, "A stream should be provided");
 
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
   mStream = aStream;
--- a/content/media/wave/nsWaveDecoder.h
+++ b/content/media/wave/nsWaveDecoder.h
@@ -175,17 +175,18 @@ class nsWaveDecoder : public nsMediaDeco
   virtual PRBool IsSeeking() const;
 
   // Report whether the decoder has reached end of playback.
   virtual PRBool IsEnded() const;
 
   // Start downloading the media at the specified URI.  The media's metadata
   // will be parsed and made available as the load progresses.
   virtual nsresult Load(nsMediaStream* aStream,
-                        nsIStreamListener** aStreamListener);
+                        nsIStreamListener** aStreamListener,
+                        nsMediaDecoder* aCloneDonor);
 
   // Called by mStream (and possibly the nsChannelToPipeListener used
   // internally by mStream) when the stream has completed loading.
   virtual void ResourceLoaded();
 
   // Called by mStream (and possibly the nsChannelToPipeListener used
   // internally by mStream) if the stream encounters a network error.
   virtual void NetworkError();
--- a/content/media/webm/nsWebMBufferedParser.cpp
+++ b/content/media/webm/nsWebMBufferedParser.cpp
@@ -33,16 +33,20 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsAlgorithm.h"
 #include "nsWebMBufferedParser.h"
+#include "nsTimeRanges.h"
+
+static const float NS_PER_S = 1e9;
+static const float MS_PER_S = 1e3;
 
 static PRUint32
 VIntLength(unsigned char aFirstByte, PRUint32* aMask)
 {
   PRUint32 count = 1;
   PRUint32 mask = 1 << 7;
   while (count < 8) {
     if ((aFirstByte & mask) != 0) {
@@ -193,8 +197,88 @@ void nsWebMBufferedParser::Append(const 
       mNextState = ANY_BLOCK_SYNC;
       break;
     }
   }
 
   NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
   mCurrentOffset += aLength;
 }
+
+void nsWebMBufferedState::CalculateBufferedForRange(nsTimeRanges* aBuffered,
+                                                    PRInt64 aStartOffset, PRInt64 aEndOffset,
+                                                    PRUint64 aTimecodeScale)
+{
+  // Find the first nsWebMTimeDataOffset at or after aStartOffset.
+  PRUint32 start;
+  mTimeMapping.GreatestIndexLtEq(aStartOffset, start);
+  if (start == mTimeMapping.Length()) {
+    return;
+  }
+
+  // Find the first nsWebMTimeDataOffset at or before aEndOffset.
+  PRUint32 end;
+  if (!mTimeMapping.GreatestIndexLtEq(aEndOffset, end) && end > 0) {
+    // No exact match, so adjust end to be the first entry before
+    // aEndOffset.
+    end -= 1;
+  }
+
+  // Range is empty.
+  if (end <= start) {
+    return;
+  }
+
+  NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset &&
+               mTimeMapping[end].mOffset <= aEndOffset,
+               "Computed time range must lie within data range.");
+  if (start > 0) {
+    NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset,
+                 "Must have found least nsWebMTimeDataOffset for start");
+  }
+  if (end < mTimeMapping.Length() - 1) {
+    NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset,
+                 "Must have found greatest nsWebMTimeDataOffset for end");
+  }
+
+  float startTime = mTimeMapping[start].mTimecode * aTimecodeScale / NS_PER_S;
+  float endTime = mTimeMapping[end].mTimecode * aTimecodeScale / NS_PER_S;
+  aBuffered->Add(startTime, endTime);
+}
+
+void nsWebMBufferedState::NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset)
+{
+  PRUint32 idx;
+  if (!mRangeParsers.GreatestIndexLtEq(aOffset, idx)) {
+    // If the incoming data overlaps an already parsed range, adjust the
+    // buffer so that we only reparse the new data.  It's also possible to
+    // have an overlap where the end of the incoming data is within an
+    // already parsed range, but we don't bother handling that other than by
+    // avoiding storing duplicate timecodes when the parser runs.
+    if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
+      // Complete overlap, skip parsing.
+      if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
+        return;
+      }
+
+      // Partial overlap, adjust the buffer to parse only the new data.
+      PRInt64 adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
+      NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
+      aBuffer += adjust;
+      aLength -= PRUint32(adjust);
+    } else {
+      mRangeParsers.InsertElementAt(idx, nsWebMBufferedParser(aOffset));
+    }
+  }
+
+  mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), aLength, mTimeMapping);
+
+  // Merge parsers with overlapping regions and clean up the remnants.
+  PRUint32 i = 0;
+  while (i + 1 < mRangeParsers.Length()) {
+    if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
+      mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
+      mRangeParsers.RemoveElementAt(i);
+    } else {
+      i += 1;
+    }
+  }
+}
--- a/content/media/webm/nsWebMBufferedParser.h
+++ b/content/media/webm/nsWebMBufferedParser.h
@@ -33,18 +33,21 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #if !defined(nsWebMBufferedParser_h_)
 #define nsWebMBufferedParser_h_
 
+#include "nsISupportsImpl.h"
 #include "nsTArray.h"
 
+class nsTimeRanges;
+
 // Stores a stream byte offset and the scaled timecode of the block at
 // that offset.  The timecode must be scaled by the stream's timecode
 // scale before use.
 struct nsWebMTimeDataOffset
 {
   nsWebMTimeDataOffset(PRInt64 aOffset, PRUint64 aTimecode)
     : mOffset(aOffset), mTimecode(aTimecode)
   {}
@@ -199,9 +202,36 @@ private:
   // Number of bytes of mBlockTimecode left to read.
   PRUint32 mBlockTimecodeLength;
 
   // Count of bytes left to skip before resuming parse at mNextState.
   // Mostly used to skip block payload data after reading a block timecode.
   PRUint32 mSkipBytes;
 };
 
+class nsWebMBufferedState
+{
+  NS_INLINE_DECL_REFCOUNTING(nsWebMBufferedState)
+
+public:
+  nsWebMBufferedState() {
+    MOZ_COUNT_CTOR(nsWebMBufferedState);
+  }
+
+  ~nsWebMBufferedState() {
+    MOZ_COUNT_DTOR(nsWebMBufferedState);
+  }
+
+  void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset);
+  void CalculateBufferedForRange(nsTimeRanges* aBuffered,
+                                 PRInt64 aStartOffset, PRInt64 aEndOffset,
+                                 PRUint64 aTimecodeScale);
+
+private:
+  // Sorted (by offset) map of data offsets to timecodes.  Populated
+  // on the main thread as data is received and parsed by nsWebMBufferedParsers.
+  nsTArray<nsWebMTimeDataOffset> mTimeMapping;
+
+  // Sorted (by offset) live parser instances.  Main thread only.
+  nsTArray<nsWebMBufferedParser> mRangeParsers;
+};
+
 #endif
--- a/content/media/webm/nsWebMReader.cpp
+++ b/content/media/webm/nsWebMReader.cpp
@@ -118,17 +118,16 @@ nsWebMReader::nsWebMReader(nsBuiltinDeco
   : nsBuiltinDecoderReader(aDecoder),
   mContext(nsnull),
   mPacketCount(0),
   mChannels(0),
   mVideoTrack(0),
   mAudioTrack(0),
   mAudioStartMs(-1),
   mAudioSamples(0),
-  mTimecodeScale(1000000),
   mHasVideo(PR_FALSE),
   mHasAudio(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsWebMReader);
 }
 
 nsWebMReader::~nsWebMReader()
 {
@@ -142,27 +141,33 @@ nsWebMReader::~nsWebMReader()
   vorbis_block_clear(&mVorbisBlock);
   vorbis_dsp_clear(&mVorbisDsp);
   vorbis_info_clear(&mVorbisInfo);
   vorbis_comment_clear(&mVorbisComment);
 
   MOZ_COUNT_DTOR(nsWebMReader);
 }
 
-nsresult nsWebMReader::Init()
+nsresult nsWebMReader::Init(nsBuiltinDecoderReader* aCloneDonor)
 {
   if (vpx_codec_dec_init(&mVP8, &vpx_codec_vp8_dx_algo, NULL, 0)) {
     return NS_ERROR_FAILURE;
   }
 
   vorbis_info_init(&mVorbisInfo);
   vorbis_comment_init(&mVorbisComment);
   memset(&mVorbisDsp, 0, sizeof(vorbis_dsp_state));
   memset(&mVorbisBlock, 0, sizeof(vorbis_block));
 
+  if (aCloneDonor) {
+    mBufferedState = static_cast<nsWebMReader*>(aCloneDonor)->mBufferedState;
+  } else {
+    mBufferedState = new nsWebMBufferedState;
+  }
+
   return NS_OK;
 }
 
 nsresult nsWebMReader::ResetDecode()
 {
   mAudioSamples = 0;
   mAudioStartMs = -1;
   nsresult res = NS_OK;
@@ -207,22 +212,16 @@ nsresult nsWebMReader::ReadMetadata()
   uint64_t duration = 0;
   r = nestegg_duration(mContext, &duration);
   if (r == 0) {
     MonitorAutoExit exitReaderMon(mMonitor);
     MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
     mDecoder->GetStateMachine()->SetDuration(duration / NS_PER_MS);
   }
 
-  r = nestegg_tstamp_scale(mContext, &mTimecodeScale);
-  if (r == -1) {
-    Cleanup();
-    return NS_ERROR_FAILURE;
-  }
-
   unsigned int ntracks = 0;
   r = nestegg_track_count(mContext, &ntracks);
   if (r == -1) {
     Cleanup();
     return NS_ERROR_FAILURE;
   }
 
   mInfo.mHasAudio = PR_FALSE;
@@ -700,115 +699,46 @@ nsresult nsWebMReader::Seek(PRInt64 aTar
   PRUint32 trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
   int r = nestegg_track_seek(mContext, trackToSeek, aTarget * NS_PER_MS);
   if (r != 0) {
     return NS_ERROR_FAILURE;
   }
   return DecodeToTarget(aTarget);
 }
 
-void nsWebMReader::CalculateBufferedForRange(nsTimeRanges* aBuffered,
-                                             PRInt64 aStartOffset, PRInt64 aEndOffset)
-{
-  // Find the first nsWebMTimeDataOffset at or after aStartOffset.
-  PRUint32 start;
-  mTimeMapping.GreatestIndexLtEq(aStartOffset, start);
-  if (start == mTimeMapping.Length()) {
-    return;
-  }
-
-  // Find the first nsWebMTimeDataOffset at or before aEndOffset.
-  PRUint32 end;
-  if (!mTimeMapping.GreatestIndexLtEq(aEndOffset, end) && end > 0) {
-    // No exact match, so adjust end to be the first entry before
-    // aEndOffset.
-    end -= 1;
-  }
-
-  // Range is empty.
-  if (end <= start) {
-    return;
-  }
-
-  NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset &&
-               mTimeMapping[end].mOffset <= aEndOffset,
-               "Computed time range must lie within data range.");
-  if (start > 0) {
-    NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset,
-                 "Must have found least nsWebMTimeDataOffset for start");
-  }
-  if (end < mTimeMapping.Length() - 1) {
-    NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset,
-                 "Must have found greatest nsWebMTimeDataOffset for end");
-  }
-
-  float startTime = mTimeMapping[start].mTimecode * mTimecodeScale / NS_PER_S;
-  float endTime = mTimeMapping[end].mTimecode * mTimecodeScale / NS_PER_S;
-  aBuffered->Add(startTime, endTime);
-}
-
 nsresult nsWebMReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   nsMediaStream* stream = mDecoder->GetCurrentStream();
 
+  PRUint64 timecodeScale;
+  if (!mContext || nestegg_tstamp_scale(mContext, &timecodeScale) == -1) {
+    return NS_OK;
+  }
+
   // Special case completely cached files.  This also handles local files.
   if (stream->IsDataCachedToEndOfStream(0)) {
     uint64_t duration = 0;
-    if (mContext && nestegg_duration(mContext, &duration) == 0) {
+    if (nestegg_duration(mContext, &duration) == 0) {
       aBuffered->Add(aStartTime / MS_PER_S, duration / NS_PER_S);
     }
   } else {
     PRInt64 startOffset = stream->GetNextCachedData(0);
     while (startOffset >= 0) {
       PRInt64 endOffset = stream->GetCachedDataEnd(startOffset);
       NS_ASSERTION(startOffset < endOffset, "Cached range invalid");
 
-      CalculateBufferedForRange(aBuffered, startOffset, endOffset);
+      mBufferedState->CalculateBufferedForRange(aBuffered, startOffset, endOffset, timecodeScale);
 
       // Advance to the next cached data range.
       startOffset = stream->GetNextCachedData(endOffset);
       NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
                    "Next cached range invalid");
     }
   }
 
   return NS_OK;
 }
 
 void nsWebMReader::NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset)
 {
-  PRUint32 idx;
-  if (!mRangeParsers.GreatestIndexLtEq(aOffset, idx)) {
-    // If the incoming data overlaps an already parsed range, adjust the
-    // buffer so that we only reparse the new data.  It's also possible to
-    // have an overlap where the end of the incoming data is within an
-    // already parsed range, but we don't bother handling that other than by
-    // avoiding storing duplicate timecodes when the parser runs.
-    if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
-      // Complete overlap, skip parsing.
-      if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
-        return;
-      }
-
-      // Partial overlap, adjust the buffer to parse only the new data.
-      PRInt64 adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
-      NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
-      aBuffer += adjust;
-      aLength -= PRUint32(adjust);
-    } else {
-      mRangeParsers.InsertElementAt(idx, nsWebMBufferedParser(aOffset));
-    }
-  }
-
-  mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), aLength, mTimeMapping);
-
-  // Merge parsers with overlapping regions and clean up the remnants.
-  PRUint32 i = 0;
-  while (i + 1 < mRangeParsers.Length()) {
-    if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
-      mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
-      mRangeParsers.RemoveElementAt(i);
-    } else {
-      i += 1;
-    }
-  }
+  mBufferedState->NotifyDataArrived(aBuffer, aLength, aOffset);
 }
--- a/content/media/webm/nsWebMReader.h
+++ b/content/media/webm/nsWebMReader.h
@@ -90,24 +90,23 @@ class PacketQueue : private nsDeque {
   
   void Reset() {
     while (GetSize() > 0) {
       nestegg_free_packet(PopFront());
     }
   }
 };
 
-
 class nsWebMReader : public nsBuiltinDecoderReader
 {
 public:
   nsWebMReader(nsBuiltinDecoder* aDecoder);
   ~nsWebMReader();
 
-  virtual nsresult Init();
+  virtual nsresult Init(nsBuiltinDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
   virtual PRBool DecodeAudioData();
 
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.  
   virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
                                   PRInt64 aTimeThreshold);
@@ -187,29 +186,18 @@ private:
   PRUint32 mAudioTrack;
 
   // Time in ms of the start of the first audio sample we've decoded.
   PRInt64 mAudioStartMs;
 
   // Number of samples we've decoded since decoding began at mAudioStartMs.
   PRUint64 mAudioSamples;
 
-  // Time in ns by which raw timecodes from the media must be scaled to
-  // produce absolute timecodes.  Used by CalculateBufferedForRange.
-  PRUint64 mTimecodeScale;
-
-  // Update aBuffered with the time range for the given data range.
-  void CalculateBufferedForRange(nsTimeRanges* aBuffered,
-                                 PRInt64 aStartOffset, PRInt64 aEndOffset);
-
-  // Sorted (by offset) map of data offsets to timecodes.  Populated
-  // on the main thread as data is received and parsed by nsWebMBufferedParsers.
-  nsTArray<nsWebMTimeDataOffset> mTimeMapping;
-
-  // Sorted (by offset) live parser instances.  Main thread only.
-  nsTArray<nsWebMBufferedParser> mRangeParsers;
+  // Parser state and computed offset-time mappings.  Shared by multiple
+  // readers when decoder has been cloned.  Main thread only.
+  nsRefPtr<nsWebMBufferedState> mBufferedState;
 
   // Booleans to indicate if we have audio and/or video data
   PRPackedBool mHasVideo;
   PRPackedBool mHasAudio;
 };
 
 #endif