Backing out bug 445087 due to reftest failures
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 05 Feb 2009 23:51:45 +1300
changeset 24673 90e5130c458e70ed57a25326f8c06f4d1e0a3136
parent 24671 5de9f1e51c68b15bcec8c936a065334f6a3ec50b (current diff)
parent 24672 c802d7a5b897eda3abd7d7fb6f2cfa91e3fe7012 (diff)
child 24674 8b72dc73cfa13b822356b7e784f8889e35fc644a
push id5155
push userrocallahan@mozilla.com
push dateThu, 05 Feb 2009 10:52:14 +0000
treeherdermozilla-central@90e5130c458e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs445087
milestone1.9.2a1pre
Backing out bug 445087 due to reftest failures
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -106,16 +106,21 @@ public:
   // Called by the video decoder object, on the main thread,
   // when the resource has a network error during loading.
   void NetworkError();
 
   // Called by the video decoder object, on the main thread,
   // when the video playback has ended.
   void PlaybackEnded();
 
+  // Called by the decoder object, on the main thread, when
+  // approximately enough of the resource has been loaded to play
+  // through without pausing for buffering.
+  void CanPlayThrough();
+
   // Called by the video decoder object, on the main thread,
   // when the resource has started seeking.
   void SeekStarted();
 
   // Called by the video decoder object, on the main thread,
   // when the resource has completed seeking.
   void SeekCompleted();
 
@@ -124,23 +129,16 @@ public:
   void Paint(gfxContext* aContext, const gfxRect& aRect);
 
   // Dispatch events
   nsresult DispatchSimpleEvent(const nsAString& aName);
   nsresult DispatchProgressEvent(const nsAString& aName);
   nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
   nsresult DispatchAsyncProgressEvent(const nsAString& aName);
 
-  // Called by the decoder when some data has been downloaded or
-  // buffering/seeking has ended. aNextFrameAvailable is true when
-  // the data for the next frame is available. This method will
-  // decide whether to set the ready state to HAVE_CURRENT_DATA,
-  // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA.
-  void UpdateReadyStateForData(PRBool aNextFrameAvailable);
-
   // Use this method to change the mReadyState member, so required
   // events can be fired.
   void ChangeReadyState(nsMediaReadyState aState);
 
   // Gets the pref media.enforce_same_site_origin, which determines
   // if we should check Access Controls, or allow cross domain loads.
   PRBool ShouldCheckAllowOrigin();
 
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -961,16 +961,18 @@ void nsHTMLMediaElement::MetadataLoaded(
     }
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
   }
 }
 
 void nsHTMLMediaElement::FirstFrameLoaded()
 {
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
+  mLoadedFirstFrame = PR_TRUE;
+  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadeddata"));
 }
 
 void nsHTMLMediaElement::ResourceLoaded()
 {
   mBegun = PR_FALSE;
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADED;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
   DispatchProgressEvent(NS_LITERAL_STRING("load"));
@@ -988,16 +990,21 @@ void nsHTMLMediaElement::NetworkError()
 void nsHTMLMediaElement::PlaybackEnded()
 {
   NS_ASSERTION(mDecoder->IsEnded(), "Decoder fired ended, but not in ended state");
   mBegun = PR_FALSE;
   mPaused = PR_TRUE;
   DispatchSimpleEvent(NS_LITERAL_STRING("ended"));
 }
 
+void nsHTMLMediaElement::CanPlayThrough()
+{
+  ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+}
+
 void nsHTMLMediaElement::SeekStarted()
 {
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
 }
 
 void nsHTMLMediaElement::SeekCompleted()
 {
   mPlayingBeforeSeek = PR_FALSE;
@@ -1005,133 +1012,50 @@ void nsHTMLMediaElement::SeekCompleted()
 }
 
 PRBool nsHTMLMediaElement::ShouldCheckAllowOrigin()
 {
   return nsContentUtils::GetBoolPref("media.enforce_same_site_origin",
                                      PR_TRUE);
 }
 
-// Number of bytes to add to the download size when we're computing
-// when the download will finish --- a safety margin in case bandwidth
-// or other conditions are worse than expected
-static const PRInt32 gDownloadSizeSafetyMargin = 1000000;
-
-void nsHTMLMediaElement::UpdateReadyStateForData(PRBool aNextFrameAvailable)
-{
-  if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
-    NS_ASSERTION(!aNextFrameAvailable, "How can we have a frame but no metadata?");
-    // The arrival of more data can't change us out of this state.
-    return;
-  }
-
-  if (!aNextFrameAvailable && !mDecoder->IsEnded()) {
-    ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
-    return;
-  }
-
-  // Now see if we should set HAVE_ENOUGH_DATA
-  nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
-  if (stats.mTotalBytes < 0 || stats.mTotalBytes == stats.mDownloadPosition) {
-    // If it's something we don't know the size of, then we can't
-    // make an estimate, so let's just go straight to HAVE_ENOUGH_DATA,
-    // since otherwise autoplay elements will never play.
-    ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
-    return;
-  }
-
-  if (stats.mDownloadRateReliable && stats.mPlaybackRateReliable) {
-    PRInt64 bytesToDownload = stats.mTotalBytes - stats.mDownloadPosition;
-    PRInt64 bytesToPlayback = stats.mTotalBytes - stats.mPlaybackPosition;
-    double timeToDownload =
-      (bytesToDownload + gDownloadSizeSafetyMargin)/stats.mDownloadRate;
-    double timeToPlay = bytesToPlayback/stats.mPlaybackRate;
-    LOG(PR_LOG_DEBUG, ("Download rate=%f, playback rate=%f, timeToDownload=%f, timeToPlay=%f",
-        stats.mDownloadRate, stats.mPlaybackRate, timeToDownload, timeToPlay));
-    if (timeToDownload <= timeToPlay) {
-      ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
-      return;
-    }
-  }
-
-  ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
-}
-
 void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
 {
-  nsMediaReadyState oldState = mReadyState;
+  // Handle raising of "waiting" event during seek (see 4.8.10.9)
+  if (mPlayingBeforeSeek && aState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA)
+    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
 
-  // Handle raising of "waiting" event during seek (see 4.8.10.9)
-  if (mPlayingBeforeSeek && oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
-  }
- 
   mReadyState = aState;
   if (mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
-    switch (mReadyState) {
+    switch(mReadyState) {
     case nsIDOMHTMLMediaElement::HAVE_NOTHING:
-      if (oldState != mReadyState) {
-        LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_NOTHING"));
-      }
-      break;
-
-    case nsIDOMHTMLMediaElement::HAVE_METADATA:
-      if (oldState != mReadyState) {
-        LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_METADATA"));
-      }
+      LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_NOTHING"));
       break;
 
     case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
-      if (oldState != mReadyState) {
-        LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_CURRENT_DATA"));
-      }
-      if (oldState <= nsIDOMHTMLMediaElement::HAVE_METADATA &&
-          !mLoadedFirstFrame) {
-        DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadeddata"));
-        mLoadedFirstFrame = PR_TRUE;
-      }
+      LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_CURRENT_DATA"));
       break;
- 
+
     case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
-      if (oldState != mReadyState) {
-        LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_FUTURE_DATA"));
-      }
-      if (oldState <= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
-        DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
-        if (IsPotentiallyPlaying()) {
-          DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
-        }
-      }
+      DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
+      LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_FUTURE_DATA"));
       break;
- 
+
     case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
       DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplaythrough"));
-      if (oldState != mReadyState) {
-        LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_ENOUGH_DATA"));
-      }
-      if (oldState <= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
-        DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
-      }
-      if (oldState <= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
-        DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplaythrough"));
-      }
       if (mAutoplaying &&
           mPaused &&
           HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
         mPaused = PR_FALSE;
         if (mDecoder) {
           mDecoder->Play();
         }
         DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
       }
       LOG(PR_LOG_DEBUG, ("Ready state changed to HAVE_ENOUGH_DATA"));
-      if (oldState <= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
-          IsPotentiallyPlaying()) {
-        DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
-      }
       break;
     }
   }
 }
 
 void nsHTMLMediaElement::Paint(gfxContext* aContext, const gfxRect& aRect) 
 {
   if (mDecoder)
@@ -1172,19 +1096,19 @@ nsresult nsHTMLMediaElement::DispatchPro
 
   nsCOMPtr<nsIDOMEvent> event;
   nsresult rv = docEvent->CreateEvent(NS_LITERAL_STRING("ProgressEvent"), getter_AddRefs(event));
   NS_ENSURE_SUCCESS(rv, rv);
   
   nsCOMPtr<nsIDOMProgressEvent> progressEvent(do_QueryInterface(event));
   NS_ENSURE_TRUE(progressEvent, NS_ERROR_FAILURE);
 
-  nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
+  PRInt64 length = mDecoder->GetTotalBytes();
   rv = progressEvent->InitProgressEvent(aName, PR_TRUE, PR_TRUE,
-    stats.mTotalBytes >= 0, stats.mDownloadPosition, stats.mTotalBytes);
+                                        length >= 0, mDecoder->GetBytesLoaded(), length);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool dummy;
   return target->DispatchEvent(event, &dummy);  
 }
 
 nsresult nsHTMLMediaElement::DoneAddingChildren(PRBool aHaveNotified)
 {
--- a/content/media/video/public/nsChannelReader.h
+++ b/content/media/video/public/nsChannelReader.h
@@ -66,16 +66,24 @@ public:
   // Cancel any blocking request currently in progress and cause that
   // request to return an error. Call on main thread only.
   void Cancel();
 
   // Return the number of bytes buffered from the file. This can safely
   // be read without blocking.
   PRUint32 Available();
 
+  // Return average number of bytes per second that the 
+  // download of the media resource is achieving.
+  float DownloadRate();
+
+  // Return average number of bytes per second that the 
+  // playback of the media resource is achieving.
+  float PlaybackRate();
+
   // Suspend any downloads that are in progress.
   void Suspend();
 
   // Resume any downloads that have been suspended.
   void Resume();
 
   nsIPrincipal* GetCurrentPrincipal();
   
@@ -83,11 +91,12 @@ public:
   OggPlayErrorCode initialise(int aBlock);
   OggPlayErrorCode destroy();
   size_t io_read(char* aBuffer, size_t aCount);
   int io_seek(long aOffset, int aWhence);
   long io_tell();  
   
 public:
   nsMediaStream mStream;
+  unsigned long mCurrentPosition;
 };
 
 #endif
--- a/content/media/video/public/nsChannelToPipeListener.h
+++ b/content/media/video/public/nsChannelToPipeListener.h
@@ -39,16 +39,20 @@
 
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsIRequestObserver.h"
 #include "nsIStreamListener.h"
 #include "nsIPrincipal.h"
 
+// Constant for download and playback rates that are unknown, or otherwise
+// unable to be computed.
+#define NS_MEDIA_UNKNOWN_RATE -1.0
+
 class nsMediaDecoder;
 
 /* 
    Reads all data on the input stream of a channel and
    writes it to a pipe. This allows a seperate thread to
    read data from a channel running on the main thread
 */
 class nsChannelToPipeListener : public nsIStreamListener
@@ -62,27 +66,48 @@ class nsChannelToPipeListener : public n
   // IStreamListener
   NS_DECL_NSISTREAMLISTENER
 
   public:
   // If aSeeking is PR_TRUE then this listener was created as part of a
   // seek request and is expecting a byte range partial result. aOffset
   // is the offset in bytes that this listener started reading from.
   nsChannelToPipeListener(nsMediaDecoder* aDecoder,
-                          PRBool aSeeking = PR_FALSE);
+                          PRBool aSeeking = PR_FALSE,
+                          PRInt64 aOffset = 0);
   nsresult Init();
   nsresult GetInputStream(nsIInputStream** aStream);
   void Stop();
   void Cancel();
 
+  // Return the download rate in bytes per second. Returns 
+  // less than zero if the download has complated.
+  double BytesPerSecond() const;
+
   nsIPrincipal* GetCurrentPrincipal();
 
 private:
   nsCOMPtr<nsIInputStream> mInput;
   nsCOMPtr<nsIOutputStream> mOutput;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsRefPtr<nsMediaDecoder> mDecoder;
 
+  // Interval when download started. Used in
+  // computing bytes per second download rate.
+  PRIntervalTime mIntervalStart;
+
+  // Interval when last downloaded bytes occurred. Used in computer
+  // bytes per second download rate.
+  PRIntervalTime mIntervalEnd;
+
+  // Offset from the beginning of the resource where the listener
+  // started reading. This is used for computing the current file
+  // position for progress events.
+  PRInt64 mOffset;
+
+  // Total bytes transferred so far. Used for computing download rates.
+  PRInt64 mTotalBytes;
+
   // PR_TRUE if this listener is expecting a byte range request result
   PRPackedBool mSeeking;
 };
 
 #endif
--- a/content/media/video/public/nsMediaDecoder.h
+++ b/content/media/video/public/nsMediaDecoder.h
@@ -40,30 +40,28 @@
 
 #include "nsIObserver.h"
 #include "nsIPrincipal.h"
 #include "nsSize.h"
 #include "prlog.h"
 #include "gfxContext.h"
 #include "gfxRect.h"
 #include "nsITimer.h"
-#include "prinrval.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gVideoDecoderLog;
 #define LOG(type, msg) PR_LOG(gVideoDecoderLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
 class nsHTMLMediaElement;
 
 // All methods of nsMediaDecoder must be called from the main thread only
-// with the exception of SetRGBData and GetStatistics, which can be
-// called from any thread.
+// with the exception of SetRGBData. The latter can be called from any thread.
 class nsMediaDecoder : public nsIObserver
 {
  public:
   nsMediaDecoder();
   virtual ~nsMediaDecoder();
 
   // Initialize the logging object
   static nsresult InitLogger();
@@ -135,49 +133,23 @@ class nsMediaDecoder : public nsIObserve
   // Call from any thread safely. Return PR_TRUE if we are currently
   // seeking in the media resource.
   virtual PRBool IsSeeking() const = 0;
 
   // Return PR_TRUE if the decoder has reached the end of playback.
   // Call in the main thread only.
   virtual PRBool IsEnded() const = 0;
 
-  struct Statistics {
-    // Estimate of the current playback rate (bytes/second).
-    double mPlaybackRate;
-    // Estimate of the current download rate (bytes/second)
-    double mDownloadRate;
-    // Total length of media stream in bytes; -1 if not known
-    PRInt64 mTotalBytes;
-    // Current position of the download, in bytes. This position (and
-    // the other positions) should only increase unless the current
-    // playback position is explicitly changed. This may require
-    // some fudging by the decoder if operations like seeking or finding the
-    // duration require seeks in the underlying stream.
-    PRInt64 mDownloadPosition;
-    // Current position of decoding, in bytes (how much of the stream
-    // has been consumed)
-    PRInt64 mDecoderPosition;
-    // Current position of playback, in bytes
-    PRInt64 mPlaybackPosition;
-    // If false, then mDownloadRate cannot be considered a reliable
-    // estimate (probably because the download has only been running
-    // a short time).
-    PRPackedBool mDownloadRateReliable;
-    // If false, then mPlaybackRate cannot be considered a reliable
-    // estimate (probably because playback has only been running
-    // a short time).
-    PRPackedBool mPlaybackRateReliable;
-  };
+  // Return the current number of bytes loaded from the video file.
+  // This is used for progress events.
+  virtual PRUint64 GetBytesLoaded() = 0;
 
-  // Return statistics. This is used for progress events and other things.
-  // This can be called from any thread. It's only a snapshot of the
-  // current state, since other threads might be changing the state
-  // at any time.
-  virtual Statistics GetStatistics() = 0;
+  // Return the size of the video file in bytes. Return 0 if the
+  // size is unknown or the stream is infinite.
+  virtual PRInt64 GetTotalBytes() = 0;
 
   // Set the size of the video file in bytes.
   virtual void SetTotalBytes(PRInt64 aBytes) = 0;
 
   // Set a flag indicating whether seeking is supported
   virtual void SetSeekable(PRBool aSeekable) = 0;
 
   // Return PR_TRUE if seeking is supported.
@@ -187,42 +159,18 @@ class nsMediaDecoder : public nsIObserve
   virtual void Invalidate();
 
   // Fire progress events if needed according to the time and byte
   // constraints outlined in the specification. aTimer is PR_TRUE
   // if the method is called as a result of the progress timer rather
   // than the result of downloaded data.
   virtual void Progress(PRBool aTimer);
 
-  // Called by nsMediaStream when a seek operation happens (could be
-  // called either before or after the seek completes). Called on the main
-  // thread. This may be called as a result of the stream opening (the
-  // offset should be zero in that case).
-  // Reads from streams after a seek MUST NOT complete before
-  // NotifyDownloadSeeked has been delivered. (We assume the reads
-  // and the seeks happen on the same calling thread.)
-  virtual void NotifyDownloadSeeked(PRInt64 aOffsetBytes) = 0;
-
-  // Called by nsChannelToPipeListener or nsMediaStream when data has
-  // been received.
-  // Call on the main thread only. aBytes of data have just been received.
-  // Reads from streams MUST NOT complete before the NotifyBytesDownloaded
-  // for those bytes has been delivered. (We assume reads and seeks
-  // happen on the same calling thread.)
-  virtual void NotifyBytesDownloaded(PRInt64 aBytes) = 0;
-
-  // Called by nsChannelToPipeListener or nsMediaStream when the
-  // download has ended. Called on the main thread only. aStatus is
-  // the result from OnStopRequest.
-  virtual void NotifyDownloadEnded(nsresult aStatus) = 0;
-
-  // Called by nsMediaStream when data has been read from the stream
-  // for playback.
-  // Call on any thread. aBytes of data have just been consumed.
-  virtual void NotifyBytesConsumed(PRInt64 aBytes) = 0;
+  // Keep track of the number of bytes downloaded
+  virtual void UpdateBytesDownloaded(PRUint64 aBytes) = 0;
 
   // Cleanup internal data structures. Must be called on the main
   // thread by the owning object before that object disposes of this object.  
   virtual void Shutdown();
 
   // Suspend any media downloads that are in progress. Called by the
   // media element when it is sent to the bfcache. Call on the main
   // thread only.
@@ -251,80 +199,16 @@ protected:
   // This is the only nsMediaDecoder method that may be called 
   // from threads other than the main thread.
   // It must be called with the mVideoUpdateLock held.
   void SetRGBData(PRInt32 aWidth, 
                   PRInt32 aHeight, 
                   float aFramerate, 
                   unsigned char* aRGBBuffer);
 
-  /**
-   * This class is useful for estimating rates of data passing through
-   * some channel. The idea is that activity on the channel "starts"
-   * and "stops" over time. At certain times data passes through the
-   * channel (usually while the channel is active; data passing through
-   * an inactive channel is ignored). The GetRate() function computes
-   * an estimate of the "current rate" of the channel, which is some
-   * kind of average of the data passing through over the time the
-   * channel is active.
-   * 
-   * Timestamps and time durations are measured in PRIntervalTimes, but
-   * all methods take "now" as a parameter so the user of this class can
-   * define what the timeline means.
-   */
-  class ChannelStatistics {
-  public:
-    ChannelStatistics() { Reset(); }
-    void Reset() {
-      mLastStartTime = mAccumulatedTime = 0;
-      mAccumulatedBytes = 0;
-      mIsStarted = PR_FALSE;
-    }
-    void Start(PRIntervalTime aNow) {
-      if (mIsStarted)
-        return;
-      mLastStartTime = aNow;
-      mIsStarted = PR_TRUE;
-    }
-    void Stop(PRIntervalTime aNow) {
-      if (!mIsStarted)
-        return;
-      mAccumulatedTime += aNow - mLastStartTime;
-      mIsStarted = PR_FALSE;
-    }
-    void AddBytes(PRInt64 aBytes) {
-      if (!mIsStarted) {
-        // ignore this data, it may be related to seeking or some other
-        // operation we don't care about
-        return;
-      }
-      mAccumulatedBytes += aBytes;
-    }
-    double GetRateAtLastStop(PRPackedBool* aReliable) {
-      *aReliable = mAccumulatedTime >= PR_TicksPerSecond();
-      return double(mAccumulatedBytes)*PR_TicksPerSecond()/mAccumulatedTime;
-    }
-    double GetRate(PRIntervalTime aNow, PRPackedBool* aReliable) {
-      PRIntervalTime time = mAccumulatedTime;
-      if (mIsStarted) {
-        time += aNow - mLastStartTime;
-      }
-      *aReliable = time >= PR_TicksPerSecond();
-      NS_ASSERTION(time >= 0, "Time wraparound?");
-      if (time <= 0)
-        return 0.0;
-      return double(mAccumulatedBytes)*PR_TicksPerSecond()/time;
-    }
-  private:
-    PRInt64        mAccumulatedBytes;
-    PRIntervalTime mAccumulatedTime;
-    PRIntervalTime mLastStartTime;
-    PRPackedBool   mIsStarted;
-  };
-
 protected:
   // Timer used for updating progress events 
   nsCOMPtr<nsITimer> mProgressTimer;
 
   // The element is not reference counted. Instead the decoder is
   // notified when it is able to be used. It should only ever be
   // accessed from the main thread.
   nsHTMLMediaElement* mElement;
--- a/content/media/video/public/nsMediaStream.h
+++ b/content/media/video/public/nsMediaStream.h
@@ -84,23 +84,22 @@ public:
    * *aStreamListener to null, if it doesn't need a listener).
    */
   virtual nsresult Open(nsIStreamListener** aStreamListener) = 0;
   virtual nsresult Close() = 0;
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes) = 0;
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset) = 0;
   virtual PRInt64  Tell() = 0;
   virtual PRUint32 Available() = 0;
+  virtual float    DownloadRate() = 0;
   virtual void     Cancel() { }
   virtual nsIPrincipal* GetCurrentPrincipal() = 0;
   virtual void     Suspend() = 0;
   virtual void     Resume() = 0;
 
-  nsMediaDecoder* Decoder() { return mDecoder; }
-
 protected:
   // This is not an nsCOMPointer to prevent a circular reference
   // between the decoder to the media stream object. The stream never
   // outlives the lifetime of the decoder.
   nsMediaDecoder* mDecoder;
 
   // Channel used to download the media data. Must be accessed
   // from the main thread only.
@@ -171,16 +170,25 @@ class nsMediaStream
   // Report the current offset in bytes from the start of the stream.
   // Can be called from any thread.
   PRInt64 Tell();
 
   // Return the number of bytes available in the stream that can be
   // read without blocking. Can be called from any thread.
   PRUint32 Available();
 
+  // Return the current download rate in bytes per second. Returns less than
+  // zero if the download has completed. Can be called from any
+  // thread.
+  float DownloadRate();
+
+  // Return the current playback rate in bytes per second. Can be
+  // called from any thread.
+  float PlaybackRate();
+
   // Cancels any currently blocking request and forces that request to
   // return an error. Call on main thread only.
   void Cancel();
 
   // Call on main thread only.
   nsIPrincipal* GetCurrentPrincipal();
 
   // Suspend any downloads that are in progress. Call on the main thread
@@ -192,11 +200,22 @@ class nsMediaStream
   void Resume();
 
  private:
   // Strategy object that is used for the handling seeking, etc
   // Accessed from any thread. Set on the Open call on the main thread
   // only. Open is always called first on the main thread before any
   // other calls from other threads.
   nsAutoPtr<nsStreamStrategy> mStreamStrategy;
+
+  // Time used for computing average playback rate. Written on the 
+  // main thread only during the Open call. Read from any thread during
+  // calls to PlaybackRate() - which can only ever happen after Open.
+  PRIntervalTime mPlaybackRateStart;
+
+  // Bytes downloaded for average playback rate computation. Initialized
+  // on the main thread during Open(). After that it is read and written
+  // possibly on a different thread, but exclusively from that
+  // thread. In the case of the Ogg Decoder, it is the Decoder thread.
+  PRUint32 mPlaybackRateCount;
 };
 
 #endif
--- a/content/media/video/public/nsOggDecoder.h
+++ b/content/media/video/public/nsOggDecoder.h
@@ -320,20 +320,17 @@ class nsOggDecoder : public nsMediaDecod
   virtual void Pause();
   virtual float GetVolume();
   virtual void SetVolume(float volume);
   virtual float GetDuration();
 
   virtual void GetCurrentURI(nsIURI** aURI);
   virtual nsIPrincipal* GetCurrentPrincipal();
 
-  virtual void NotifyBytesDownloaded(PRInt64 aBytes);
-  virtual void NotifyDownloadSeeked(PRInt64 aOffsetBytes);
-  virtual void NotifyDownloadEnded(nsresult aStatus);
-  virtual void NotifyBytesConsumed(PRInt64 aBytes);
+  virtual void UpdateBytesDownloaded(PRUint64 aBytes);
 
   // Called when the video file has completed downloading.
   // Call on the main thread only.
   void ResourceLoaded();
 
   // Called if the media file encounters a network error.
   // Call on the main thread only.
   virtual void NetworkError();
@@ -353,18 +350,16 @@ class nsOggDecoder : public nsMediaDecod
   virtual void SetSeekable(PRBool aSeekable);
 
   // Return PR_TRUE if seeking is supported.
   virtual PRBool GetSeekable();
 
   // Returns the channel reader.
   nsChannelReader* GetReader() { return mReader; }
 
-  virtual Statistics GetStatistics();
-
   // Suspend any media downloads that are in progress. Called by the
   // media element when it is sent to the bfcache. Call on the main
   // thread only.
   virtual void Suspend();
 
   // Resume any media downloads that have been suspended. Called by the
   // media element when it is restored from the bfcache. Call on the
   // main thread only.
@@ -412,20 +407,32 @@ protected:
   // Called when the first frame has been loaded.
   // Call on the main thread only.
   void FirstFrameLoaded();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
+  // Return the current number of bytes loaded from the video file.
+  // This is used for progress events.
+  virtual PRUint64 GetBytesLoaded();
+
+  // Return the size of the video file in bytes.
+  // This is used for progress events.
+  virtual PRInt64 GetTotalBytes();
+
   // Buffering of data has stopped. Inform the element on the main
   // thread.
   void BufferingStopped();
 
+  // Buffering of data has started. Inform the element on the main
+  // thread.
+  void BufferingStarted();
+
   // Seeking has stopped. Inform the element on the main
   // thread.
   void SeekingStopped();
 
   // Seeking has started. Inform the element on the main
   // thread.
   void SeekingStarted();
 
@@ -435,47 +442,21 @@ protected:
   void PlaybackPositionChanged();
 
 private:
   // Register/Unregister with Shutdown Observer. 
   // Call on main thread only.
   void RegisterShutdownObserver();
   void UnregisterShutdownObserver();
 
-  // Calls mElement->UpdateReadyStateForData, telling it whether we have
-  // data for the next frame.
-  void UpdateReadyStateForData();
-
   /******
-   * The following members should be accessed with the decoder lock held.
+   * The following members should be accessed on the main thread only
    ******/
-
-  // Size of the media file in bytes. Set on the first
-  // HTTP request from nsChannelToPipe Listener. -1 if not known.
-  PRInt64 mTotalBytes;
-  // Current download position in the stream. 
-  PRInt64 mDownloadPosition;
-  // Download position to report if asked. This is the same as
-  // mDownloadPosition normally, but we don't update it while ignoring
-  // progress. This lets us avoid reporting progress changes due to reads
-  // that are only servicing our seek operations.
-  PRInt64 mProgressPosition;
-  // Current decoding position in the stream. This is where the decoder
-  // is up to consuming the stream.
-  PRInt64 mDecoderPosition;
-  // Current playback position in the stream. This is (approximately)
-  // where we're up to playing back the stream.
-  PRInt64 mPlaybackPosition;
-  // Data needed to estimate download data rate. The timeline used for
-  // this estimate is wall-clock time.
-  ChannelStatistics mDownloadStatistics;
-  // Data needed to estimate playback data rate. The timeline used for
-  // this estimate is "decode time" (where the "current time" is the
-  // time of the last decoded video frame).
-  ChannelStatistics mPlaybackStatistics;
+  // Total number of bytes downloaded so far. 
+  PRUint64 mBytesDownloaded;
 
   // The URI of the current resource
   nsCOMPtr<nsIURI> mURI;
 
   // Thread to handle decoding of Ogg data.
   nsCOMPtr<nsIThread> mDecodeThread;
 
   // The current playback position of the media resource in units of
@@ -492,16 +473,21 @@ private:
 
   // Position to seek to when the seek notification is received by the
   // decoding thread. Written by the main thread and read via the
   // decoding thread. Synchronised using mPlayStateMonitor. If the
   // value is negative then no seek has been requested. When a seek is
   // started this is reset to negative.
   float mRequestedSeekTime;
 
+  // Size of the media file in bytes. Set on the first non-byte range
+  // HTTP request from nsChannelToPipe Listener. Accessed on the
+  // main thread only.
+  PRInt64 mContentLength;
+
   // Duration of the media resource. Set to -1 if unknown.
   // Set when the Ogg metadata is loaded. Accessed on the main thread
   // only.
   PRInt64 mDuration;
 
   // True if we are registered with the observer service for shutdown.
   PRPackedBool mNotifyOnShutdown;
 
@@ -541,22 +527,21 @@ private:
   PlayState mPlayState;
 
   // The state to change to after a seek or load operation. It must only
   // be changed from the main thread. The decoder monitor must be acquired
   // when writing to the state, or when reading from a non-main thread.
   // Any change to the state must call NotifyAll on the monitor.
   PlayState mNextState;	
 
-  // True when we have fully loaded the resource and reported that
-  // to the element (i.e. reached NETWORK_LOADED state).
-  // Accessed on the main thread only.
+  // True when the media resource has completely loaded. Accessed on
+  // the main thread only.
   PRPackedBool mResourceLoaded;
 
   // True when seeking or otherwise moving the play position around in
   // such a manner that progress event data is inaccurate. This is set
-  // during seek and duration operations to prevent the progress indicator
+  // before a seek or during loading of metadata to prevent the progress indicator
   // from jumping around. Read/Write from any thread. Must have decode monitor
   // locked before accessing.
   PRPackedBool mIgnoreProgressData;
 };
 
 #endif
--- a/content/media/video/public/nsWaveDecoder.h
+++ b/content/media/video/public/nsWaveDecoder.h
@@ -186,45 +186,44 @@ class nsWaveDecoder : public nsMediaDeco
 
   // Called by mStream (and possibly the nsChannelToPipeListener used
   // internally by mStream) if the stream encounters a network error.
   virtual void NetworkError();
 
   // Element is notifying us that the requested playback rate has changed.
   virtual nsresult PlaybackRateChanged();
 
-  virtual void NotifyBytesDownloaded(PRInt64 aBytes);
-  virtual void NotifyDownloadSeeked(PRInt64 aOffset);
-  virtual void NotifyDownloadEnded(nsresult aStatus);
-  virtual void NotifyBytesConsumed(PRInt64 aBytes);
-
-  virtual Statistics GetStatistics();
-
+  // Getter/setter for mContentLength.
+  virtual PRInt64 GetTotalBytes();
   virtual void SetTotalBytes(PRInt64 aBytes);
 
   // Getter/setter for mSeekable.
   virtual void SetSeekable(PRBool aSeekable);
   virtual PRBool GetSeekable();
 
+  // Getter/setter for mBytesDownloaded.
+  virtual PRUint64 GetBytesLoaded();
+  virtual void UpdateBytesDownloaded(PRUint64 aBytes);
+
   // Must be called by the owning object before disposing the decoder.
   virtual void Shutdown();
 
   // Suspend any media downloads that are in progress. Called by the
   // media element when it is sent to the bfcache. Call on the main
   // thread only.
   virtual void Suspend();
 
   // Resume any media downloads that have been suspended. Called by the
   // media element when it is restored from the bfcache. Call on the
   // main thread only.
   virtual void Resume();
 
 private:
-  // Change the element's ready state as necessary
-  void UpdateReadyStateForData();
+  // Notifies the nsHTMLMediaElement that buffering has started.
+  void BufferingStarted();
 
   // Notifies the element that buffering has stopped.
   void BufferingStopped();
 
   // Notifies the element that seeking has started.
   void SeekingStarted();
 
   // Notifies the element that seeking has completed.
@@ -238,16 +237,22 @@ private:
   void PlaybackEnded();
 
   // Notifies the element that metadata loading has failed.
   void MediaErrorDecode();
 
   void RegisterShutdownObserver();
   void UnregisterShutdownObserver();
 
+  // Length of the current resource, or -1 if not available.
+  PRInt64 mContentLength;
+
+  // Total bytes downloaded by mStream so far.
+  PRUint64 mBytesDownloaded;
+
   // Volume that the audio backend will be initialized with.
   float mInitialVolume;
 
   // URI of the current resource.
   nsCOMPtr<nsIURI> mURI;
 
   // Thread that handles audio playback, including data download.
   nsCOMPtr<nsIThread> mPlaybackThread;
@@ -278,17 +283,11 @@ private:
   PRPackedBool mNotifyOnShutdown;
 
   // True if the media resource is seekable.
   PRPackedBool mSeekable;
 
   // True when the media resource has completely loaded. Accessed on
   // the main thread only.
   PRPackedBool mResourceLoaded;
-
-  // True if MetadataLoaded has been reported to the element.
-  PRPackedBool mMetadataLoadedReported;
-
-  // True if ResourceLoaded has been reported to the element.
-  PRPackedBool mResourceLoadedReported;
 };
 
 #endif
--- a/content/media/video/src/nsChannelReader.cpp
+++ b/content/media/video/src/nsChannelReader.cpp
@@ -48,16 +48,26 @@ void nsChannelReader::Cancel()
   mStream.Cancel();
 }
 
 PRUint32 nsChannelReader::Available()
 {
   return mStream.Available();
 }
 
+float nsChannelReader::DownloadRate()
+{
+  return mStream.DownloadRate();
+}
+
+float nsChannelReader::PlaybackRate()
+{
+  return mStream.PlaybackRate();
+}
+
 OggPlayErrorCode nsChannelReader::initialise(int aBlock)
 {
   return E_OGGPLAY_OK;
 }
 
 OggPlayErrorCode nsChannelReader::destroy()
 {
   mStream.Close();
@@ -76,16 +86,17 @@ void nsChannelReader::Resume()
 
 size_t nsChannelReader::io_read(char* aBuffer, size_t aCount)
 {
   PRUint32 bytes = 0;
   nsresult rv = mStream.Read(aBuffer, aCount, &bytes);
   if (!NS_SUCCEEDED(rv)) {
     return static_cast<size_t>(OGGZ_ERR_SYSTEM);
   }
+  mCurrentPosition += bytes;
   return bytes;
 }
 
 int nsChannelReader::io_seek(long aOffset, int aWhence)
 {
   nsresult rv = mStream.Seek(aWhence, aOffset);
   if (NS_SUCCEEDED(rv))
     return aOffset;
@@ -131,16 +142,17 @@ static long oggplay_channel_reader_io_te
   nsChannelReader* me = static_cast<nsChannelReader*>(aReader);
   return me->io_tell();
 }
 
 nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI,
                                nsIChannel* aChannel,
                                nsIStreamListener** aStreamListener)
 {
+  mCurrentPosition = 0;
   return mStream.Open(aDecoder, aURI, aChannel, aStreamListener);
 }
 
 nsChannelReader::~nsChannelReader()
 {
   MOZ_COUNT_DTOR(nsChannelReader);
 }
 
--- a/content/media/video/src/nsChannelToPipeListener.cpp
+++ b/content/media/video/src/nsChannelToPipeListener.cpp
@@ -44,18 +44,23 @@
 #include "nsDOMError.h"
 #include "nsHTMLMediaElement.h"
 
 #define HTTP_OK_CODE 200
 #define HTTP_PARTIAL_RESPONSE_CODE 206
 
 nsChannelToPipeListener::nsChannelToPipeListener(
     nsMediaDecoder* aDecoder,
-    PRBool aSeeking) :
+    PRBool aSeeking,
+    PRInt64 aOffset) :
   mDecoder(aDecoder),
+  mIntervalStart(0),
+  mIntervalEnd(0),
+  mOffset(aOffset),
+  mTotalBytes(0),
   mSeeking(aSeeking)
 {
 }
 
 nsresult nsChannelToPipeListener::Init() 
 {
   nsresult rv = NS_NewPipe(getter_AddRefs(mInput), 
                            getter_AddRefs(mOutput),
@@ -77,16 +82,21 @@ void nsChannelToPipeListener::Cancel()
 {
   if (mOutput)
     mOutput->Close();
 
   if (mInput)
     mInput->Close();
 }
 
+double nsChannelToPipeListener::BytesPerSecond() const
+{
+  return mOutput ? mTotalBytes / ((PR_IntervalToMilliseconds(mIntervalEnd-mIntervalStart)) / 1000.0) : NS_MEDIA_UNKNOWN_RATE;
+}
+
 nsresult nsChannelToPipeListener::GetInputStream(nsIInputStream** aStream)
 {
   NS_IF_ADDREF(*aStream = mInput);
   return NS_OK;
 }
 
 nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
@@ -98,21 +108,25 @@ nsresult nsChannelToPipeListener::OnStar
     nsresult status;
     nsresult rv = aRequest->GetStatus(&status);
     if (NS_FAILED(rv) || status == NS_ERROR_DOM_BAD_URI) {
       mDecoder->NetworkError();
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
+  mIntervalStart = PR_IntervalNow();
+  mIntervalEnd = mIntervalStart;
+  mTotalBytes = 0;
+  mDecoder->UpdateBytesDownloaded(mOffset);
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
   if (hc) {
     nsCAutoString ranges;
-    hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
-                          ranges);
+    nsresult rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
+                                        ranges);
     PRBool acceptsRanges = ranges.EqualsLiteral("bytes"); 
 
     PRUint32 responseStatus = 0; 
     hc->GetResponseStatus(&responseStatus);
     if (mSeeking && responseStatus == HTTP_OK_CODE) {
       // If we get an OK response but we were seeking, and therefore
       // expecting a partial response of HTTP_PARTIAL_RESPONSE_CODE,
       // seeking should still be possible if the server is sending
@@ -164,48 +178,56 @@ nsresult nsChannelToPipeListener::OnStar
   mDecoder->Progress(PR_FALSE);
 
   return NS_OK;
 }
 
 nsresult nsChannelToPipeListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) 
 {
   mOutput = nsnull;
-  if (mDecoder) {
-    mDecoder->NotifyDownloadEnded(aStatus);
+  if (aStatus != NS_BINDING_ABORTED && mDecoder) {
+    if (NS_SUCCEEDED(aStatus)) {
+      mDecoder->ResourceLoaded();
+    } else if (aStatus != NS_BASE_STREAM_CLOSED) {
+      mDecoder->NetworkError();
+    }
   }
   return NS_OK;
 }
 
 nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest, 
                                                 nsISupports* aContext, 
                                                 nsIInputStream* aStream,
                                                 PRUint32 aOffset,
                                                 PRUint32 aCount)
 {
   if (!mOutput)
     return NS_ERROR_FAILURE;
 
-  mDecoder->NotifyBytesDownloaded(aCount);
-
+  PRUint32 bytes = 0;
+  
   do {
-    PRUint32 bytes;
     nsresult rv = mOutput->WriteFrom(aStream, aCount, &bytes);
     if (NS_FAILED(rv))
       return rv;
     
     aCount -= bytes;
-  } while (aCount);
+    mTotalBytes += bytes;
+    aOffset += bytes;
+    mDecoder->UpdateBytesDownloaded(mOffset + aOffset);
+  } while (aCount) ;
   
   nsresult rv = mOutput->Flush();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Fire a progress events according to the time and byte constraints outlined
   // in the spec.
   mDecoder->Progress(PR_FALSE);
+
+  mIntervalEnd = PR_IntervalNow();
   return NS_OK;
 }
 
 nsIPrincipal*
 nsChannelToPipeListener::GetCurrentPrincipal()
 {
   return mPrincipal;
 }
--- a/content/media/video/src/nsMediaStream.cpp
+++ b/content/media/video/src/nsMediaStream.cpp
@@ -67,16 +67,17 @@ public:
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
   virtual PRInt64  Tell();
   virtual PRUint32 Available();
+  virtual float    DownloadRate();
   virtual void     Cancel();
   virtual nsIPrincipal* GetCurrentPrincipal();
   virtual void     Suspend();
   virtual void     Resume();
 
 private:
   // Listener attached to channel to constantly download the
   // media data asynchronously and store it in the pipe. The 
@@ -195,16 +196,22 @@ PRUint32 nsDefaultStreamStrategy::Availa
   if (!mPipeInput)
     return 0;
 
   PRUint32 count = 0;
   mPipeInput->Available(&count);
   return count;
 }
 
+float nsDefaultStreamStrategy::DownloadRate()
+{
+  nsAutoLock lock(mLock);
+  return mListener ? mListener->BytesPerSecond() : NS_MEDIA_UNKNOWN_RATE;
+}
+
 void nsDefaultStreamStrategy::Cancel()
 {
   if (mListener)
     mListener->Cancel();
 }
 
 nsIPrincipal* nsDefaultStreamStrategy::GetCurrentPrincipal()
 {
@@ -235,16 +242,17 @@ public:
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
   virtual PRInt64  Tell();
   virtual PRUint32 Available();
+  virtual float    DownloadRate();
   virtual nsIPrincipal* GetCurrentPrincipal();
   virtual void     Suspend();
   virtual void     Resume();
 
 private:
   // Seekable stream interface to file. This can be used from any
   // thread.
   nsCOMPtr<nsISeekableStream> mSeekable;
@@ -252,46 +260,16 @@ private:
   // Input stream for the media data. This can be used from any
   // thread.
   nsCOMPtr<nsIInputStream>  mInput;
 
   // Security Principal
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
-class LoadedEvent : public nsRunnable 
-{
-public:
-  LoadedEvent(nsMediaDecoder* aDecoder, PRInt64 aOffset, PRInt64 aSize) :
-    mOffset(aOffset), mSize(aSize), mDecoder(aDecoder)
-  {
-    MOZ_COUNT_CTOR(LoadedEvent);
-  }
-  ~LoadedEvent()
-  {
-    MOZ_COUNT_DTOR(LoadedEvent);
-  }
-
-  NS_IMETHOD Run() {
-    if (mOffset >= 0) {
-      mDecoder->NotifyDownloadSeeked(mOffset);
-    }
-    if (mSize > 0) {
-      mDecoder->NotifyBytesDownloaded(mSize);
-    }
-    mDecoder->NotifyDownloadEnded(NS_OK);
-    return NS_OK;
-  }
-
-private:
-  PRInt64                  mOffset;
-  PRInt64                  mSize;
-  nsRefPtr<nsMediaDecoder> mDecoder;
-};
-
 nsresult nsFileStreamStrategy::Open(nsIStreamListener** aStreamListener)
 {
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
   nsresult rv;
   if (aStreamListener) {
@@ -327,42 +305,42 @@ nsresult nsFileStreamStrategy::Open(nsIS
   if (!mSeekable) {
     // XXX The file may just be a .url or similar
     // shortcut that points to a Web site. We need to fix this by
     // doing an async open and waiting until we locate the real resource,
     // then using that (if it's still a file!).
     return NS_ERROR_FAILURE;
   }
 
+  // Get the file size and inform the decoder. Only files up to 4GB are
+  // supported here.
+  PRUint32 size;
+  rv = mInput->Available(&size);
+  if (NS_SUCCEEDED(rv)) {
+    mDecoder->SetTotalBytes(size);
+    mDecoder->UpdateBytesDownloaded(size);
+  }
+
   /* Get our principal */
   nsCOMPtr<nsIScriptSecurityManager> secMan =
     do_GetService("@mozilla.org/scriptsecuritymanager;1");
   if (secMan) {
     rv = secMan->GetChannelPrincipal(mChannel,
                                      getter_AddRefs(mPrincipal));
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
-  // Get the file size and inform the decoder. Only files up to 4GB are
-  // supported here.
-  PRUint32 size;
-  rv = mInput->Available(&size);
-  if (NS_SUCCEEDED(rv)) {
-    mDecoder->SetTotalBytes(size);
-  }
-
-  // This must happen before we return from this function, we can't
-  // defer it to the LoadedEvent because that would allow reads from
-  // the stream to complete before this notification is sent.
-  mDecoder->NotifyBytesDownloaded(size);
-
-  nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder, -1, 0);
+  // For a file stream the resource is considered loaded since there
+  // is no buffering delays, etc reading.
+  nsCOMPtr<nsIRunnable> event = 
+    NS_NEW_RUNNABLE_METHOD(nsMediaDecoder, mDecoder, ResourceLoaded); 
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  
   return NS_OK;
 }
 
 nsresult nsFileStreamStrategy::Close()
 {
   nsAutoLock lock(mLock);
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
@@ -372,45 +350,23 @@ nsresult nsFileStreamStrategy::Close()
   }
 
   return NS_OK;
 }
 
 nsresult nsFileStreamStrategy::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
 {
   nsAutoLock lock(mLock);
-  if (!mInput)
-    return NS_ERROR_FAILURE;
-  return mInput->Read(aBuffer, aCount, aBytes);
+  return mInput ? mInput->Read(aBuffer, aCount, aBytes) : NS_ERROR_FAILURE;
 }
 
 nsresult nsFileStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset) 
 {  
-  PRUint32 size = 0;
-  PRInt64 absoluteOffset = 0;
-  nsresult rv;
-  {
-    nsAutoLock lock(mLock);
-    if (!mSeekable)
-      return NS_ERROR_FAILURE;
-    rv = mSeekable->Seek(aWhence, aOffset);
-    if (NS_SUCCEEDED(rv)) {
-      mSeekable->Tell(&absoluteOffset);
-    }
-    mInput->Available(&size);
-  }
-
-  if (NS_SUCCEEDED(rv)) {
-    nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder, absoluteOffset, size);
-    // Synchronous dispatch to ensure the decoder is notified before our caller
-    // proceeds and reads occur.
-    NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
-  }
-
-  return rv;
+  nsAutoLock lock(mLock);
+  return mSeekable ? mSeekable->Seek(aWhence, aOffset) : NS_ERROR_FAILURE;
 }
 
 PRInt64 nsFileStreamStrategy::Tell()
 {
   nsAutoLock lock(mLock);
   if (!mSeekable)
     return 0;
 
@@ -425,16 +381,21 @@ PRUint32 nsFileStreamStrategy::Available
   if (!mInput)
     return 0;
 
   PRUint32 count = 0;
   mInput->Available(&count);
   return count;
 }
 
+float nsFileStreamStrategy::DownloadRate()
+{
+  return NS_MEDIA_UNKNOWN_RATE;
+}
+
 nsIPrincipal* nsFileStreamStrategy::GetCurrentPrincipal()
 {
   return mPrincipal;
 }
 
 void nsFileStreamStrategy::Suspend()
 {
   mChannel->Suspend();
@@ -459,16 +420,17 @@ public:
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream.
   virtual nsresult Open(nsIStreamListener** aListener);
   virtual nsresult Close();
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
   virtual PRInt64  Tell();
   virtual PRUint32 Available();
+  virtual float    DownloadRate();
   virtual void     Cancel();
   virtual nsIPrincipal* GetCurrentPrincipal();
   virtual void     Suspend();
   virtual void     Resume();
 
   // Return PR_TRUE if the stream has been cancelled.
   PRBool IsCancelled() const;
 
@@ -527,17 +489,17 @@ nsresult nsHttpStreamStrategy::OpenInter
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
 
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
-  mListener = new nsChannelToPipeListener(mDecoder, aOffset != 0);
+  mListener = new nsChannelToPipeListener(mDecoder, aOffset != 0, aOffset);
   NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = mListener->Init();
   NS_ENSURE_SUCCESS(rv, rv);
   
   nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(mListener);
 
   if (aStreamListener) {
@@ -577,21 +539,22 @@ nsresult nsHttpStreamStrategy::OpenInter
  
     rv = mChannel->AsyncOpen(listener, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   
   rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mDecoder->NotifyDownloadSeeked(aOffset);
+  mPosition = aOffset;
 
   return NS_OK;
 }
 
+
 nsresult nsHttpStreamStrategy::Close()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   nsAutoLock lock(mLock);
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
   }
@@ -669,53 +632,56 @@ public:
     mResult = mStrategy->OpenInternal(channel, mOffset);
     return NS_OK;
   }
 
 private:
   nsHttpStreamStrategy* mStrategy;
   nsMediaDecoder* mDecoder;
   nsIURI* mURI;
+  nsCOMPtr<nsIChannel> mChannel;
+  nsCOMPtr<nsChannelToPipeListener> mListener;
+  nsCOMPtr<nsIInputStream> mStream;
   PRInt64 mOffset;
   nsresult mResult;
 };
 
-nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
+nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset) 
 {
-  PRInt64 totalBytes = mDecoder->GetStatistics().mTotalBytes;
-
   {
     nsAutoLock lock(mLock);
     if (!mChannel || !mPipeInput) 
       return NS_ERROR_FAILURE;
 
     // When seeking liboggz will first seek to the end of the file to
     // obtain the length of the file. It immediately does a 'tell' to
     // get the position and reseeks somewhere else. This traps the seek
     // to end of file and sets mAtEOF. Tell() looks for this flag being
     // set and returns the content length.
     if(aWhence == nsISeekableStream::NS_SEEK_END && aOffset == 0) {
-      if (totalBytes == -1)
+      if (mDecoder->GetTotalBytes() == -1)
         return NS_ERROR_FAILURE;
       
       mAtEOF = PR_TRUE;
       return NS_OK;
     }
     else {
       mAtEOF = PR_FALSE;
     }
 
     // Handle cases of aWhence not being NS_SEEK_SET by converting to
     // NS_SEEK_SET
     switch (aWhence) {
     case nsISeekableStream::NS_SEEK_END: {
-      if (totalBytes == -1)
+      PRInt32 length;
+      mChannel->GetContentLength(&length);
+      if (length == -1)
         return NS_ERROR_FAILURE;
       
-      aOffset += totalBytes; 
+      aOffset -= length; 
       aWhence = nsISeekableStream::NS_SEEK_SET;
       break;
     }
     case nsISeekableStream::NS_SEEK_CUR: {
       aOffset += mPosition;
       aWhence = nsISeekableStream::NS_SEEK_SET;
       break;
     }
@@ -746,34 +712,27 @@ nsresult nsHttpStreamStrategy::Seek(PRIn
     if (NS_SUCCEEDED(rv) && bytesAhead > 0 && diff > -SEEK_VS_READ_THRESHOLD) {
       nsAutoArrayPtr<char> data(new char[bytesAhead]);
       if (!data)
         return NS_ERROR_OUT_OF_MEMORY;
     
       // Read until the read cursor reaches new seek point. If Cancel() is
       // called then the read will fail with an error so we can bail out of
       // the blocking call.
-      PRInt32 bytesRead = 0;
+      PRUint32 bytesRead = 0;
       PRUint32 bytes = 0;
       do {
         nsresult rv = mPipeInput->Read(data.get(),
                                        (bytesAhead-bytesRead),
                                        &bytes);
         NS_ENSURE_SUCCESS(rv, rv);
         NS_ENSURE_TRUE(bytes != 0, NS_ERROR_FAILURE); // Tried to read past EOF.
         mPosition += bytes;
         bytesRead += bytes;
       } while (bytesRead != bytesAhead);
-
-      // We don't need to notify the decoder here that we seeked. It will
-      // look like we just read ahead a bit. In fact, we mustn't tell
-      // the decoder that we seeked, since the seek notification might
-      // race with the "data downloaded" notification after the data was
-      // written into the pipe, so that the seek notification
-      // happens *first*, hopelessly confusing the decoder. 
       return rv;
     }
   }
 
   // Don't acquire mLock in this scope as we do a synchronous call to the main thread
   // which would deadlock if that thread is calling Close().
   nsCOMPtr<nsByteRangeEvent> event = new nsByteRangeEvent(this, mURI, aOffset);
   NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
@@ -787,33 +746,41 @@ nsresult nsHttpStreamStrategy::Seek(PRIn
 
   return rv;
 }
 
 PRInt64 nsHttpStreamStrategy::Tell()
 {
   // Handle the case of a seek to EOF by liboggz
   // (See Seek for details)
-  return mAtEOF ? mDecoder->GetStatistics().mTotalBytes : mPosition;
+  return mAtEOF ? mDecoder->GetTotalBytes() : mPosition;
 }
 
 PRUint32 nsHttpStreamStrategy::Available()
 {
   // The request pulls from the pipe, not the channels input
   // stream. This allows calling from any thread as the pipe is
   // threadsafe.
   nsAutoLock lock(mLock);
   if (!mPipeInput)
     return 0;
 
   PRUint32 count = 0;
   mPipeInput->Available(&count);
   return count;
 }
 
+float nsHttpStreamStrategy::DownloadRate()
+{
+  nsAutoLock lock(mLock);
+  if (!mListener)
+    return NS_MEDIA_UNKNOWN_RATE;
+  return mListener->BytesPerSecond();
+}
+
 void nsHttpStreamStrategy::Cancel()
 {
   mCancelled = PR_TRUE;
   if (mListener)
     mListener->Cancel();
 }
 
 PRBool nsHttpStreamStrategy::IsCancelled() const
@@ -834,17 +801,18 @@ void nsHttpStreamStrategy::Suspend()
   mChannel->Suspend();
 }
 
 void nsHttpStreamStrategy::Resume()
 {
   mChannel->Resume();
 }
 
-nsMediaStream::nsMediaStream()
+nsMediaStream::nsMediaStream()  :
+  mPlaybackRateCount(0)
 {
   NS_ASSERTION(NS_IsMainThread(), 
 	       "nsMediaStream created on non-main thread");
   MOZ_COUNT_CTOR(nsMediaStream);
 }
 
 nsMediaStream::~nsMediaStream()
 {
@@ -874,33 +842,34 @@ nsresult nsMediaStream::Open(nsMediaDeco
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
   if (hc) 
     mStreamStrategy = new nsHttpStreamStrategy(aDecoder, channel, aURI);
   else if (fc) 
     mStreamStrategy = new nsFileStreamStrategy(aDecoder, channel, aURI);
   else
     mStreamStrategy = new nsDefaultStreamStrategy(aDecoder, channel, aURI);
 
+  mPlaybackRateCount = 0;
+  mPlaybackRateStart = PR_IntervalNow();
+
   return mStreamStrategy->Open(aListener);
 }
 
 nsresult nsMediaStream::Close()
 {
   NS_ASSERTION(NS_IsMainThread(), 
 	       "nsMediaStream::Close called on non-main thread");
 
   return mStreamStrategy->Close();
 }
 
 nsresult nsMediaStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
 {
   nsresult rv = mStreamStrategy->Read(aBuffer, aCount, aBytes);
-  if (NS_SUCCEEDED(rv)) {
-    mStreamStrategy->Decoder()->NotifyBytesConsumed(*aBytes);
-  }
+  mPlaybackRateCount += *aBytes;    
   return rv;
 }
 
 nsresult nsMediaStream::Seek(PRInt32 aWhence, PRInt64 aOffset) 
 {
   return mStreamStrategy->Seek(aWhence, aOffset);
 }
 
@@ -909,16 +878,28 @@ PRInt64 nsMediaStream::Tell()
   return mStreamStrategy->Tell();
 }
 
 PRUint32 nsMediaStream::Available()
 {
   return mStreamStrategy->Available();
 }
 
+float nsMediaStream::DownloadRate()
+{
+  return mStreamStrategy->DownloadRate();
+}
+
+float nsMediaStream::PlaybackRate()
+{
+  PRIntervalTime now = PR_IntervalNow();
+  PRUint32 interval = PR_IntervalToMilliseconds(now - mPlaybackRateStart);
+  return static_cast<float>(mPlaybackRateCount) * 1000 / interval;
+}
+
 void nsMediaStream::Cancel()
 {
   NS_ASSERTION(NS_IsMainThread(), 
 	       "nsMediaStream::Cancel called on non-main thread");
 
   // In the Http strategy case the cancel will cause the http
   // channel's listener to close the pipe, forcing an i/o error on any
   // blocked read. This will allow the decode thread to complete the
--- a/content/media/video/src/nsOggDecoder.cpp
+++ b/content/media/video/src/nsOggDecoder.cpp
@@ -146,18 +146,16 @@ public:
     {
       PRUint32 length = mAudioData.Length();
       if (length == 0)
         return;
 
       aStream->Write(mAudioData.Elements(), length);
     }
 
-    // The position in the stream where this frame ended, in bytes
-    PRInt64 mEndStreamPosition;
     nsAutoArrayPtr<unsigned char> mVideoData;
     nsTArray<float> mAudioData;
     int mVideoWidth;
     int mVideoHeight;
     float mDecodedFrameTime;
     float mTime;
     OggPlayStreamInfo mState;
   };
@@ -193,22 +191,22 @@ public:
       NS_ASSERTION(!mEmpty, "FrameQueue is empty");
 
       FrameData* result = mQueue[mHead];
       mHead = (mHead + 1) % OGGPLAY_BUFFER_SIZE;
       mEmpty = mHead == mTail;
       return result;
     }
 
-    PRBool IsEmpty() const
+    PRBool IsEmpty()
     {
       return mEmpty;
     }
 
-    PRBool IsFull() const
+    PRBool IsFull()
     {
       return !mEmpty && mHead == mTail;
     }
 
   private:
     FrameData* mQueue[OGGPLAY_BUFFER_SIZE];
     PRInt32 mHead;
     PRInt32 mTail;
@@ -295,22 +293,16 @@ public:
   float GetVolume();
   void SetVolume(float aVolume);
 
   // Clear the flag indicating that a playback position change event
   // is currently queued. This is called from the main thread and must
   // be called with the decode monitor held.
   void ClearPositionChangeFlag();
 
-  // Must be called with the decode monitor held. Can be called by main
-  // thread.
-  PRBool HaveNextFrameData() const {
-    return !mDecodedFrames.IsEmpty();
-  }
-
 protected:
   // Convert the OggPlay frame information into a format used by Gecko
   // (RGB for video, float for sound, etc).The decoder monitor must be
   // acquired in the scope of calls to these functions. They must be
   // called only when the current state > DECODING_METADATA.
   void HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayVideoData* aVideoData);
   void HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize);
 
@@ -357,19 +349,18 @@ private:
   nsOggDecoder* mDecoder;
 
   // The OggPlay handle. Synchronisation of calls to oggplay functions
   // are handled by liboggplay. We control the lifetime of this
   // object, destroying it in our destructor.
   OggPlay* mPlayer;
 
   // Frame data containing decoded video/audio for the frame the
-  // current frame and the previous frame. Always accessed with monitor
-  // held. Written only via the decoder thread, but can be tested on
-  // main thread via HaveNextFrameData.
+  // current frame and the previous frame. Accessed only via the
+  // decoder thread.
   FrameQueue mDecodedFrames;
 
   // The time that playback started from the system clock. This is used
   // for synchronising frames.  It is reset after a seek as the mTime member
   // of FrameData is reset to start at 0 from the first frame after a seek.
   // Accessed only via the decoder thread.
   PRIntervalTime mPlayStartTime;
 
@@ -407,27 +398,23 @@ private:
   PRInt32 mAudioTrack;
 
   // Time that buffering started. Used for buffering timeout and only
   // accessed in the decoder thread.
   PRIntervalTime mBufferingStart;
 
   // Number of bytes to buffer when buffering. Only accessed in the
   // decoder thread.
-  PRInt64 mBufferingBytes;
+  PRUint32 mBufferingBytes;
 
   // The time value of the last decoded video frame. Used for
   // computing the sleep period between frames for a/v sync.
   // Read/Write from the decode thread only.
   float mLastFrameTime;
 
-  // The decoder position of the end of the last decoded video frame.
-  // Read/Write from the decode thread only.
-  PRInt64 mLastFramePosition;
-
   // *****
   // The follow fields are accessed by the decoder thread or
   // the main thread.
   // *****
 
   // The decoder monitor must be obtained before modifying this state.
   // NotifyAll on the monitor must be called when the state is changed by
   // the main thread so the decoder thread can wake up.
@@ -486,17 +473,16 @@ nsOggDecodeStateMachine::nsOggDecodeStat
   mVideoTrack(-1),
   mFramerate(0.0),
   mAudioRate(0),
   mAudioChannels(0),
   mAudioTrack(-1),
   mBufferingStart(0),
   mBufferingBytes(0),
   mLastFrameTime(0),
-  mLastFramePosition(-1),
   mState(DECODER_STATE_DECODING_METADATA),
   mSeekTime(0.0),
   mCurrentFrameTime(0.0),
   mVolume(1.0),
   mDuration(-1),
   mContentLength(-1),
   mSeekable(PR_TRUE),
   mPositionChangeQueued(PR_FALSE)
@@ -525,28 +511,17 @@ nsOggDecodeStateMachine::FrameData* nsOg
     return nsnull;
 
   FrameData* frame = new FrameData();
   if (!frame) {
     return nsnull;
   }
 
   frame->mTime = mLastFrameTime;
-  frame->mEndStreamPosition = mDecoder->mDecoderPosition;
   mLastFrameTime += mCallbackPeriod;
-
-  if (mLastFramePosition >= 0) {
-    NS_ASSERTION(frame->mEndStreamPosition >= mLastFramePosition,
-                 "Playback positions must not decrease without an intervening reset");
-    mDecoder->mPlaybackStatistics.Start(frame->mTime*PR_TicksPerSecond());
-    mDecoder->mPlaybackStatistics.AddBytes(frame->mEndStreamPosition - mLastFramePosition);
-    mDecoder->mPlaybackStatistics.Stop(mLastFrameTime*PR_TicksPerSecond());
-  }
-  mLastFramePosition = frame->mEndStreamPosition;
-
   int num_tracks = oggplay_get_num_tracks(mPlayer);
   float audioTime = 0.0;
   float videoTime = 0.0;
 
   if (mVideoTrack != -1 &&
       num_tracks > mVideoTrack &&
       oggplay_callback_info_get_type(info[mVideoTrack]) == OGGPLAY_YUV_VIDEO) {
     OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mVideoTrack]);
@@ -668,17 +643,16 @@ void nsOggDecodeStateMachine::PlayFrame(
       if (time >= frame->mTime) {
         // Audio for the current frame is played, but video for the next frame
         // is displayed, to account for lag from the time the audio is written
         // to when it is played. This will go away when we move to a/v sync
         // using the audio hardware clock.
         PlayAudio(frame);
         mDecodedFrames.Pop();
         PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
-        mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
         UpdatePlaybackPosition(frame->mDecodedFrameTime);
         delete frame;
       }
       else {
         // If the queue of decoded frame is full then we wait for the
         // approximate time until the next frame. 
         if (mDecodedFrames.IsFull()) {
           mon.Wait(PR_MillisecondsToInterval(PRInt64((frame->mTime - time)*1000)));
@@ -849,37 +823,34 @@ void nsOggDecodeStateMachine::Shutdown()
 {
   // oggplay_prepare_for_close cannot be undone. Once called, the
   // mPlayer object cannot decode any more frames. Once we've entered
   // the shutdown state here there's no going back.
   nsAutoMonitor mon(mDecoder->GetMonitor());
   if (mPlayer) {
     oggplay_prepare_for_close(mPlayer);
   }
-  LOG(PR_LOG_DEBUG, ("Changed state to SHUTDOWN"));
   mState = DECODER_STATE_SHUTDOWN;
   mon.NotifyAll();
 }
 
 void nsOggDecodeStateMachine::Decode()
 {
   // When asked to decode, switch to decoding only if
   // we are currently buffering.
   nsAutoMonitor mon(mDecoder->GetMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
-    LOG(PR_LOG_DEBUG, ("Changed state from BUFFERING to DECODING"));
     mState = DECODER_STATE_DECODING;
   }
 }
 
 void nsOggDecodeStateMachine::Seek(float aTime)
 {
   nsAutoMonitor mon(mDecoder->GetMonitor());
   mSeekTime = aTime;
-  LOG(PR_LOG_DEBUG, ("Changed state to SEEKING (to %f)", aTime));
   mState = DECODER_STATE_SEEKING;
 }
 
 nsresult nsOggDecodeStateMachine::Run()
 {
   nsChannelReader* reader = mDecoder->GetReader();
   NS_ENSURE_TRUE(reader, NS_ERROR_NULL_POINTER);
   while (PR_TRUE) {
@@ -889,17 +860,16 @@ nsresult nsOggDecodeStateMachine::Run()
       return NS_OK;
 
     case DECODER_STATE_DECODING_METADATA:
       mon.Exit();
       LoadOggHeaders();
       mon.Enter();
       
       if (mState == DECODER_STATE_DECODING_METADATA) {
-        LOG(PR_LOG_DEBUG, ("Changed state from DECODING_METADATA to DECODING_FIRSTFRAME"));
         mState = DECODER_STATE_DECODING_FIRSTFRAME;
       }
       break;
 
     case DECODER_STATE_DECODING_FIRSTFRAME:
       {
         OggPlayErrorCode r;
         do {
@@ -910,89 +880,77 @@ nsresult nsOggDecodeStateMachine::Run()
 
         if (mState == DECODER_STATE_SHUTDOWN)
           continue;
 
         mLastFrameTime = 0;
         FrameData* frame = NextFrame();
         if (frame) {
           mDecodedFrames.Push(frame);
-          mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
           UpdatePlaybackPosition(frame->mDecodedFrameTime);
           PlayVideo(frame);
         }
 
         nsCOMPtr<nsIRunnable> event =
           NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, FirstFrameLoaded);
         NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
 
         if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
-          LOG(PR_LOG_DEBUG, ("Changed state from DECODING_FIRSTFRAME to DECODING"));
           mState = DECODER_STATE_DECODING;
         }
       }
       break;
 
     case DECODER_STATE_DECODING:
       {
-        PRBool bufferExhausted = PR_FALSE;
-
-        if (!mDecodedFrames.IsFull()) {
-          PRInt64 initialDownloadPosition = mDecoder->mDownloadPosition;
-
-          mon.Exit();
-          OggPlayErrorCode r = DecodeFrame();
-          mon.Enter();
-
-          // Check whether decoding that frame required us to read data
-          // that wasn't available at the start of the frame. That means
-          // we should probably start buffering.
-          bufferExhausted =
-            mDecoder->mDecoderPosition > initialDownloadPosition;
-
-          if (mState != DECODER_STATE_DECODING)
-            continue;
-
-          // Get the decoded frame and store it in our queue of decoded frames
-          FrameData* frame = NextFrame();
-          if (frame) {
-            mDecodedFrames.Push(frame);
-          }
-
-          if (r != E_OGGPLAY_CONTINUE &&
-              r != E_OGGPLAY_USER_INTERRUPT &&
-              r != E_OGGPLAY_TIMEOUT)  {
-            LOG(PR_LOG_DEBUG, ("Changed state from DECODING to COMPLETED"));
-            mState = DECODER_STATE_COMPLETED;
-          }
-        }
-
-        // Show at least the first frame if we're not playing
-        // so we have a poster frame on initial load and after seek.
-        if (!mPlaying && !mDecodedFrames.IsEmpty()) {
-          PlayVideo(mDecodedFrames.Peek());
-        }
-
-        if (bufferExhausted && mState == DECODER_STATE_DECODING &&
-            mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING &&
-            (mDecoder->mTotalBytes < 0 ||
-             mDecoder->mDownloadPosition < mDecoder->mTotalBytes)) {
-          // There is at most one frame in the queue and there's
-          // more data to load. Let's buffer to make sure we can play a
-          // decent amount of video in the future.
-          if (mPlaying) {
-            StopPlayback();
+        // Before decoding check if we should buffer more data
+        if (reader->DownloadRate() >= 0 &&
+            reader->Available() < reader->PlaybackRate() * BUFFERING_SECONDS_LOW_WATER_MARK) {
+          if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
+            if (mPlaying) {
+              StopPlayback();
+            }
           }
 
           mBufferingStart = PR_IntervalNow();
-          double playbackRate = mDecoder->GetStatistics().mPlaybackRate;
-          mBufferingBytes = BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
+          mBufferingBytes = PRUint32(BUFFERING_RATE(reader->PlaybackRate()) * BUFFERING_WAIT);
           mState = DECODER_STATE_BUFFERING;
-          LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING (%d bytes)", PRInt32(mBufferingBytes)));
-        } else {
+
+          nsCOMPtr<nsIRunnable> event =
+            NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStarted);
+          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        }
+        else {
+          if (!mDecodedFrames.IsFull()) {
+            mon.Exit();
+            OggPlayErrorCode r = DecodeFrame();
+            mon.Enter();
+
+            if (mState != DECODER_STATE_DECODING)
+              continue;
+
+            // Get the decoded frame and store it in our queue of decoded frames
+            FrameData* frame = NextFrame();
+            if (frame) {
+              mDecodedFrames.Push(frame);
+            }
+
+            if (r != E_OGGPLAY_CONTINUE &&
+                r != E_OGGPLAY_USER_INTERRUPT &&
+                r != E_OGGPLAY_TIMEOUT)  {
+              mState = DECODER_STATE_COMPLETED;
+            }
+          }
+
+          // Show at least the first frame if we're not playing
+          // so we have a poster frame on initial load and after seek.
+          if (!mPlaying && !mDecodedFrames.IsEmpty()) {
+            PlayVideo(mDecodedFrames.Peek());
+          }
+
           PlayFrame();
         }
       }
       break;
 
     case DECODER_STATE_SEEKING:
       {
         // During the seek, don't have a lock on the decoder state,
@@ -1005,31 +963,28 @@ nsresult nsOggDecodeStateMachine::Run()
         // during the time when we didn't have the lock.
         float seekTime = mSeekTime;
         mDecoder->StopProgressUpdates();
         mon.Exit();
         nsCOMPtr<nsIRunnable> startEvent = 
           NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStarted);
         NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
         
-        LOG(PR_LOG_DEBUG, ("Entering oggplay_seek(%f)", seekTime));
         oggplay_seek(mPlayer, ogg_int64_t(seekTime * 1000));
-        LOG(PR_LOG_DEBUG, ("Leaving oggplay_seek"));
 
         // Reactivate all tracks. Liboggplay deactivates tracks when it
         // reads to the end of stream, but they must be reactivated in order
         // to start reading from them again.
         for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
          if (oggplay_set_track_active(mPlayer, i) < 0)  {
             LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
           }
         }
 
         mon.Enter();
-        mLastFramePosition = mDecoder->mDecoderPosition;
         mDecoder->StartProgressUpdates();
         if (mState == DECODER_STATE_SHUTDOWN)
           continue;
 
         // Remove all frames decoded prior to seek from the queue
         while (!mDecodedFrames.IsEmpty()) {
           delete mDecodedFrames.Pop();
         }
@@ -1054,53 +1009,49 @@ nsresult nsOggDecodeStateMachine::Run()
         }
         mon.Exit();
         nsCOMPtr<nsIRunnable> stopEvent = 
           NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStopped);
         NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);        
         mon.Enter();
 
         if (mState == DECODER_STATE_SEEKING && mSeekTime == seekTime) {
-          LOG(PR_LOG_DEBUG, ("Changed state from SEEKING (to %f) to DECODING", seekTime));
           mState = DECODER_STATE_DECODING;
         }
       }
       break;
 
     case DECODER_STATE_BUFFERING:
-      {
-        PRIntervalTime now = PR_IntervalNow();
-        if ((PR_IntervalToMilliseconds(now - mBufferingStart) < BUFFERING_WAIT*1000) &&
-            reader->Available() < mBufferingBytes &&
-            (mDecoder->mTotalBytes < 0 || mDecoder->mDownloadPosition < mDecoder->mTotalBytes)) {
-          LOG(PR_LOG_DEBUG, 
-              ("In buffering: buffering data until %d bytes available or %d milliseconds", 
-               PRUint32(mBufferingBytes - reader->Available()),
-               BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(now - mBufferingStart))));
-          mon.Wait(PR_MillisecondsToInterval(1000));
-          if (mState == DECODER_STATE_SHUTDOWN)
-            continue;
-        } else {
-          LOG(PR_LOG_DEBUG, ("Changed state from BUFFERING to DECODING"));
-          mState = DECODER_STATE_DECODING;
-        }
+      if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart) < BUFFERING_WAIT*1000) &&
+          reader->DownloadRate() >= 0 &&            
+          reader->Available() < mBufferingBytes) {
+        LOG(PR_LOG_DEBUG, 
+            ("Buffering data until %d bytes available or %d milliseconds", 
+             mBufferingBytes - reader->Available(),
+             BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart))));
+        mon.Wait(PR_MillisecondsToInterval(1000));
+        if (mState == DECODER_STATE_SHUTDOWN)
+          continue;
+      }
+      else {
+        mState = DECODER_STATE_DECODING;
+      }
 
-        if (mState != DECODER_STATE_BUFFERING) {
-          nsCOMPtr<nsIRunnable> event = 
-            NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStopped);
-          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-          if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
-            if (!mPlaying) {
-              StartPlayback();
-            }
+      if (mState != DECODER_STATE_BUFFERING) {
+        nsCOMPtr<nsIRunnable> event = 
+          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStopped);
+        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
+          if (!mPlaying) {
+            StartPlayback();
           }
         }
+      }
 
-        break;
-      }
+      break;
 
     case DECODER_STATE_COMPLETED:
       {
         while (mState == DECODER_STATE_COMPLETED &&
                !mDecodedFrames.IsEmpty()) {
           PlayFrame();
           if (mState != DECODER_STATE_SHUTDOWN) {
             // Wait for the time of one frame so we don't tight loop
@@ -1110,19 +1061,17 @@ nsresult nsOggDecodeStateMachine::Run()
           }
         }
 
         if (mState != DECODER_STATE_COMPLETED)
           continue;
 
         if (mAudioStream) {
           mon.Exit();
-          LOG(PR_LOG_DEBUG, ("Begin nsAudioStream::Drain"));
           mAudioStream->Drain();
-          LOG(PR_LOG_DEBUG, ("End nsAudioStream::Drain"));
           mon.Enter();
           if (mState != DECODER_STATE_COMPLETED)
             continue;
         }
 
         nsCOMPtr<nsIRunnable> event =
           NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
         NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
@@ -1251,24 +1200,21 @@ float nsOggDecoder::GetDuration()
      return static_cast<float>(mDuration) / 1000.0;
   }
 
   return std::numeric_limits<float>::quiet_NaN();
 }
 
 nsOggDecoder::nsOggDecoder() :
   nsMediaDecoder(),
-  mTotalBytes(-1),
-  mDownloadPosition(0),
-  mProgressPosition(0),
-  mDecoderPosition(0),
-  mPlaybackPosition(0),
+  mBytesDownloaded(0),
   mCurrentTime(0.0),
   mInitialVolume(0.0),
   mRequestedSeekTime(-1.0),
+  mContentLength(-1),
   mNotifyOnShutdown(PR_FALSE),
   mSeekable(PR_TRUE),
   mReader(0),
   mMonitor(0),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
   mResourceLoaded(PR_FALSE),
   mIgnoreProgressData(PR_FALSE)
@@ -1277,17 +1223,17 @@ nsOggDecoder::nsOggDecoder() :
 }
 
 PRBool nsOggDecoder::Init(nsHTMLMediaElement* aElement)
 {
   mMonitor = nsAutoMonitor::NewMonitor("media.decoder");
   return mMonitor && nsMediaDecoder::Init(aElement);
 }
 
-void nsOggDecoder::Shutdown()
+void nsOggDecoder::Shutdown() 
 {
   mShuttingDown = PR_TRUE;
 
   ChangeState(PLAY_STATE_SHUTDOWN);
   nsMediaDecoder::Shutdown();
 
   Stop();
 }
@@ -1301,20 +1247,17 @@ nsOggDecoder::~nsOggDecoder()
 nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
                             nsIStreamListener** aStreamListener)
 {
   // Reset Stop guard flag flag, else shutdown won't occur properly when
   // reusing decoder.
   mStopping = PR_FALSE;
 
   // Reset progress member variables
-  mDownloadPosition = 0;
-  mProgressPosition = 0;
-  mDecoderPosition = 0;
-  mPlaybackPosition = 0;
+  mBytesDownloaded = 0;
   mResourceLoaded = PR_FALSE;
 
   NS_ASSERTION(!mReader, "Didn't shutdown properly!");
   NS_ASSERTION(!mDecodeStateMachine, "Didn't shutdown properly!");
   NS_ASSERTION(!mDecodeThread, "Didn't shutdown properly!");
 
   if (aStreamListener) {
     *aStreamListener = nsnull;
@@ -1333,29 +1276,27 @@ nsresult nsOggDecoder::Load(nsIURI* aURI
     nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mURI));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   RegisterShutdownObserver();
 
   mReader = new nsChannelReader();
   NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
-  mDownloadStatistics.Reset();
-  mDownloadStatistics.Start(PR_IntervalNow());
 
   nsresult rv = mReader->Init(this, mURI, aChannel, aStreamListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = NS_NewThread(getter_AddRefs(mDecodeThread));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mDecodeStateMachine = new nsOggDecodeStateMachine(this);
   {
     nsAutoMonitor mon(mMonitor);
-    mDecodeStateMachine->SetContentLength(mTotalBytes);
+    mDecodeStateMachine->SetContentLength(mContentLength);
     mDecodeStateMachine->SetSeekable(mSeekable);
   }
 
   ChangeState(PLAY_STATE_LOADING);
 
   return mDecodeThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
 }
 
@@ -1441,17 +1382,16 @@ void nsOggDecoder::Stop()
   if (mStopping)
     return;
 
   mStopping = PR_TRUE;
 
   ChangeState(PLAY_STATE_ENDED);
 
   StopProgress();
-  mDownloadStatistics.Stop(PR_IntervalNow());
 
   // Force any outstanding seek and byterange requests to complete
   // to prevent shutdown from deadlocking.
   if (mReader) {
     mReader->Cancel();
   }
 
   // Shutdown must be on called the mDecodeStateMachine before deleting.
@@ -1563,57 +1503,62 @@ void nsOggDecoder::FirstFrameLoaded()
     if (mRequestedSeekTime >= 0.0) {
       ChangeState(PLAY_STATE_SEEKING);
     }
     else {
       ChangeState(mNextState);
     }
   }
 
-  if (!mResourceLoaded && mDownloadPosition == mTotalBytes) {
+  if (!mResourceLoaded && mBytesDownloaded == mContentLength) {
     ResourceLoaded();
   }
 }
 
 void nsOggDecoder::ResourceLoaded()
 {
   // Don't handle ResourceLoaded if we are shutting down, or if
   // we need to ignore progress data due to seeking (in the case
   // that the seek results in reaching end of file, we get a bogus call
   // to ResourceLoaded).
   if (mShuttingDown)
     return;
 
+  PRBool ignoreProgress = PR_FALSE;
+
   {
     // If we are seeking or loading then the resource loaded notification we get
     // should be ignored, since it represents the end of the seek request.
     nsAutoMonitor mon(mMonitor);
-    if (mIgnoreProgressData || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
+    ignoreProgress = mIgnoreProgressData;
+    if (ignoreProgress || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
       return;
+  }
 
-    Progress(PR_FALSE);
+  Progress(PR_FALSE);
 
-    // Note that mTotalBytes should not be -1 now; NotifyDownloadEnded
-    // should have set it to the download position.
-    NS_ASSERTION(mDownloadPosition == mTotalBytes, "Wrong byte count");
+  // If we know the content length, set the bytes downloaded to this
+  // so the final progress event gets the correct final value.
+  if (mContentLength >= 0) {
+    mBytesDownloaded = mContentLength;
+  }
 
-    mResourceLoaded = PR_TRUE;
-    StopProgress();
-  }
+  mResourceLoaded = PR_TRUE;
+  StopProgress();
 
   // Ensure the final progress event gets fired
   if (mElement) {
     mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
     mElement->ResourceLoaded();
   }
 }
 
 void nsOggDecoder::NetworkError()
 {
-  if (mStopping || mShuttingDown)
+  if (mShuttingDown)
     return;
 
   if (mElement)
     mElement->NetworkError();
   Stop();
 }
 
 PRBool nsOggDecoder::IsSeeking() const
@@ -1643,135 +1588,62 @@ NS_IMETHODIMP nsOggDecoder::Observe(nsIS
 {
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
     Shutdown();
   }
 
   return NS_OK;
 }
 
-nsMediaDecoder::Statistics
-nsOggDecoder::GetStatistics()
+PRUint64 nsOggDecoder::GetBytesLoaded()
 {
-  Statistics result;
+  return mBytesDownloaded;
+}
 
-  nsAutoMonitor mon(mMonitor);
-  result.mDownloadRate =
-    mDownloadStatistics.GetRate(PR_IntervalNow(), &result.mDownloadRateReliable);
-  if (mDuration >= 0 && mTotalBytes >= 0) {
-    result.mPlaybackRate = double(mTotalBytes)*1000.0/mDuration;
-    result.mPlaybackRateReliable = PR_TRUE;
-  } else {
-    result.mPlaybackRate =
-      mPlaybackStatistics.GetRateAtLastStop(&result.mPlaybackRateReliable);
-  }
-  result.mTotalBytes = mTotalBytes;
-  // Use mProgressPosition here because we don't want changes in
-  // mDownloadPosition due to intermediate seek operations etc to be
-  // reported in progress events
-  result.mDownloadPosition = mProgressPosition;
-  result.mDecoderPosition = mDecoderPosition;
-  result.mPlaybackPosition = mPlaybackPosition;
-  return result;
+PRInt64 nsOggDecoder::GetTotalBytes()
+{
+  return mContentLength;
 }
 
 void nsOggDecoder::SetTotalBytes(PRInt64 aBytes)
 {
-  nsAutoMonitor mon(mMonitor);
-
-  // Servers could lie to us about the size of the resource, so make
-  // sure we don't set mTotalBytes to less than what we've already
-  // downloaded
-  mTotalBytes = PR_MAX(mDownloadPosition, aBytes);
+  mContentLength = aBytes;
   if (mDecodeStateMachine) {
-    mDecodeStateMachine->SetContentLength(mTotalBytes);
-  }
+    nsAutoMonitor mon(mMonitor);
+    mDecodeStateMachine->SetContentLength(aBytes);
+  } 
 }
 
-void nsOggDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
-{
-  NS_ASSERTION(NS_IsMainThread(), 
-               "nsOggDecoder::NotifyBytesDownloaded called on non-main thread");   
-  {
-    nsAutoMonitor mon(mMonitor);
-
-    mDownloadPosition += aBytes;
-    if (mTotalBytes >= 0) {
-      // Ensure that mDownloadPosition <= mTotalBytes
-      mTotalBytes = PR_MAX(mTotalBytes, mDownloadPosition);
-    }
-    if (!mIgnoreProgressData) {
-      mDownloadStatistics.AddBytes(aBytes);
-      mProgressPosition = mDownloadPosition;
-    }
-  }
-
-  UpdateReadyStateForData();
-}
-
-void nsOggDecoder::NotifyDownloadSeeked(PRInt64 aOffsetBytes)
+void nsOggDecoder::UpdateBytesDownloaded(PRUint64 aBytes)
 {
   nsAutoMonitor mon(mMonitor);
-  // Don't change mProgressPosition here, since mIgnoreProgressData is set
-  mDownloadPosition = mDecoderPosition = mPlaybackPosition = aOffsetBytes;
+
   if (!mIgnoreProgressData) {
-    mProgressPosition = mDownloadPosition;
-  }
-  if (mTotalBytes >= 0) {
-    // Ensure that mDownloadPosition <= mTotalBytes
-    mTotalBytes = PR_MAX(mTotalBytes, mDownloadPosition);
-  }
-}
-
-void nsOggDecoder::NotifyDownloadEnded(nsresult aStatus)
-{
-  if (aStatus == NS_BINDING_ABORTED)
-    return;
-
-  {
-    nsAutoMonitor mon(mMonitor);
-    mDownloadStatistics.Stop(PR_IntervalNow());
-    if (NS_SUCCEEDED(aStatus)) {
-      // Update total bytes now we know the end of the stream
-      mTotalBytes = mDownloadPosition;
-    }
+    mBytesDownloaded = aBytes;
   }
-
-  if (NS_SUCCEEDED(aStatus)) {
-    ResourceLoaded();
-  } else if (aStatus != NS_BASE_STREAM_CLOSED) {
-    NetworkError();
-  }
-  UpdateReadyStateForData();
-}
-
-void nsOggDecoder::NotifyBytesConsumed(PRInt64 aBytes)
-{
-  nsAutoMonitor mon(mMonitor);
-  if (!mIgnoreProgressData) {
-    mDecoderPosition += aBytes;
-  }
-}
-
-void nsOggDecoder::UpdateReadyStateForData()
-{
-  if (!mElement || mShuttingDown || !mDecodeStateMachine)
-    return;
-
-  PRBool haveNextFrame;
-  {
-    nsAutoMonitor mon(mMonitor);
-    haveNextFrame = mDecodeStateMachine->HaveNextFrameData();
-  }
-  mElement->UpdateReadyStateForData(haveNextFrame);
 }
 
 void nsOggDecoder::BufferingStopped()
 {
-  UpdateReadyStateForData();
+  if (mShuttingDown)
+    return;
+
+  if (mElement) {
+    mElement->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
+  }
+}
+
+void nsOggDecoder::BufferingStarted()
+{
+  if (mShuttingDown)
+    return;
+
+  if (mElement) {
+    mElement->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
+  }
 }
 
 void nsOggDecoder::SeekingStopped()
 {
   if (mShuttingDown)
     return;
 
   {
@@ -1782,17 +1654,16 @@ void nsOggDecoder::SeekingStopped()
     if (mRequestedSeekTime >= 0.0)
       ChangeState(PLAY_STATE_SEEKING);
     else
       ChangeState(mNextState);
   }
 
   if (mElement) {
     mElement->SeekCompleted();
-    UpdateReadyStateForData();
   }
 }
 
 void nsOggDecoder::SeekingStarted()
 {
   if (mShuttingDown)
     return;
 
@@ -1927,35 +1798,29 @@ void nsOggDecoder::SetSeekable(PRBool aS
 
 PRBool nsOggDecoder::GetSeekable()
 {
   return mSeekable;
 }
 
 void nsOggDecoder::Suspend()
 {
-  mDownloadStatistics.Stop(PR_IntervalNow());
   if (mReader) {
     mReader->Suspend();
   }
 }
 
 void nsOggDecoder::Resume()
 {
   if (mReader) {
     mReader->Resume();
   }
-  mDownloadStatistics.Start(PR_IntervalNow());
 }
 
 void nsOggDecoder::StopProgressUpdates()
 {
   mIgnoreProgressData = PR_TRUE;
-  mDownloadStatistics.Stop(PR_IntervalNow());
 }
 
 void nsOggDecoder::StartProgressUpdates()
 {
   mIgnoreProgressData = PR_FALSE;
-  // Resync progress position now
-  mProgressPosition = mDownloadPosition;
-  mDownloadStatistics.Start(PR_IntervalNow());
 }
--- a/content/media/video/src/nsWaveDecoder.cpp
+++ b/content/media/video/src/nsWaveDecoder.cpp
@@ -144,44 +144,26 @@ public:
 
   // Returns true if the state machine is seeking.  Threadsafe.
   PRBool IsSeeking();
 
   // Returns true if the state machine has reached the end of playback.  Threadsafe.
   PRBool IsEnded();
 
   // Called by the decoder to indicate that the media stream has closed.
-  // aAtEnd is true if we read to the end of the file.
-  void StreamEnded(PRBool aAtEnd);
+  void StreamEnded();
 
   // Main state machine loop. Runs forever, until shutdown state is reached.
   NS_IMETHOD Run();
 
   // Called by the decoder when the SeekStarted event runs.  This ensures
   // the current time offset of the state machine is updated to the new seek
   // position at the appropriate time.
   void UpdateTimeOffset(float aTime);
 
-  // Called by the decoder, on the main thread.
-  nsMediaDecoder::Statistics GetStatistics();
-
-  // Called on the main thread only
-  void SetTotalBytes(PRInt64 aBytes);
-  // Called on the main thread
-  void NotifyBytesDownloaded(PRInt64 aBytes);
-  // Called on the main thread
-  void NotifyDownloadSeeked(PRInt64 aOffset);
-  // Called on the main thread
-  void NotifyDownloadEnded(nsresult aStatus);
-  // Called on any thread
-  void NotifyBytesConsumed(PRInt64 aBytes);
-
-  // Called by the main thread
-  PRBool HasPendingData();
-
 private:
   // Change the current state and wake the playback thread if it is waiting
   // on mMonitor.  Used by public member functions called from both threads,
   // so must hold mMonitor.  Threadsafe.
   void ChangeState(State aState);
 
   // Create and initialize audio stream using current audio parameters.
   void OpenAudioStream();
@@ -298,29 +280,16 @@ private:
   State mState;
 
   // A queued state transition.  This is used to record the next state
   // transition when play or pause is requested during seeking or metadata
   // loading to ensure a completed metadata load or seek returns to the most
   // recently requested state on completion.
   State mNextState;
 
-  // Length of the current resource, or -1 if not available.
-  PRInt64 mTotalBytes;
-  // Current download position in the stream.
-  // NOTE: because we don't have to read when we seek, there is no need
-  // to track a separate "progress position" which ignores download
-  // position changes due to reads servicing seeks.
-  PRInt64 mDownloadPosition;
-  // Current playback position in the stream.
-  PRInt64 mPlaybackPosition;
-  // Data needed to estimate download data rate. The channel timeline is
-  // wall-clock time.
-  nsMediaDecoder::ChannelStatistics mDownloadStatistics;
-
   // Volume that the audio backend will be initialized with.
   float mInitialVolume;
 
   // Time position (in seconds) to offset current time from audio stream.
   // Set when the seek started event runs and when the stream is closed
   // during shutdown.
   float mTimeOffset;
 
@@ -350,27 +319,23 @@ nsWaveStateMachine::nsWaveStateMachine(n
     mChannels(0),
     mSampleSize(0),
     mSampleFormat(nsAudioStream::FORMAT_S16_LE),
     mWaveLength(0),
     mWavePCMOffset(0),
     mMonitor(nsnull),
     mState(STATE_LOADING_METADATA),
     mNextState(STATE_PAUSED),
-    mTotalBytes(-1),
-    mDownloadPosition(0),
-    mPlaybackPosition(0),
     mInitialVolume(aInitialVolume),
     mTimeOffset(0.0),
     mExpectMoreData(PR_TRUE),
     mSeekTime(0.0),
     mMetadataValid(PR_FALSE)
 {
   mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
-  mDownloadStatistics.Start(PR_IntervalNow());
 }
 
 nsWaveStateMachine::~nsWaveStateMachine()
 {
   nsAutoMonitor::DestroyMonitor(mMonitor);
 }
 
 void
@@ -438,21 +403,22 @@ nsWaveStateMachine::Seek(float aTime)
 }
 
 float
 nsWaveStateMachine::GetDuration()
 {
   nsAutoMonitor monitor(mMonitor);
   if (mMetadataValid) {
     PRUint32 length = mWaveLength;
+    PRInt64 contentLength = mDecoder->GetTotalBytes();
     // If the decoder has a valid content length, and it's shorter than the
     // expected length of the PCM data, calculate the playback duration from
     // the content length rather than the expected PCM data length.
-    if (mTotalBytes >= 0 && mTotalBytes - mWavePCMOffset < length) {
-      length = mTotalBytes - mWavePCMOffset;
+    if (contentLength >= 0 && contentLength - mWavePCMOffset < length) {
+      length = contentLength - mWavePCMOffset;
     }
     return BytesToTime(length);
   }
   return std::numeric_limits<float>::quiet_NaN();
 }
 
 float
 nsWaveStateMachine::GetCurrentTime()
@@ -475,33 +441,20 @@ nsWaveStateMachine::IsSeeking()
 PRBool
 nsWaveStateMachine::IsEnded()
 {
   nsAutoMonitor monitor(mMonitor);
   return mState == STATE_ENDED || mState == STATE_SHUTDOWN;
 }
 
 void
-nsWaveStateMachine::StreamEnded(PRBool aAtEnd)
+nsWaveStateMachine::StreamEnded()
 {
   nsAutoMonitor monitor(mMonitor);
   mExpectMoreData = PR_FALSE;
-
-  // If we know the content length, set the bytes downloaded to this
-  // so the final progress event gets the correct final value.
-  if (mTotalBytes >= 0) {
-    mDownloadPosition = mTotalBytes;
-  }
-}
-
-PRBool
-nsWaveStateMachine::HasPendingData()
-{
-  nsAutoMonitor monitor(mMonitor);
-  return mPlaybackPosition < mDownloadPosition;
 }
 
 NS_IMETHODIMP
 nsWaveStateMachine::Run()
 {
   // Monitor is held by this thread almost permanently, but must be manually
   // dropped during long operations to prevent the main thread from blocking
   // when calling methods on the state machine object.
@@ -533,52 +486,57 @@ nsWaveStateMachine::Run()
           if (event) {
             NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
           }
           ChangeState(newState);
         }
       }
       break;
 
-    case STATE_BUFFERING: {
-      PRIntervalTime now = PR_IntervalNow();
-      if ((PR_IntervalToMilliseconds(now - mBufferingStart) < mBufferingWait) &&
+    case STATE_BUFFERING:
+      if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart) < mBufferingWait) &&
+          mStream->DownloadRate() >= 0 &&
           mStream->Available() < mBufferingBytes) {
-        LOG(PR_LOG_DEBUG, ("Buffering data until %d bytes or %d milliseconds\n",
+        LOG(PR_LOG_DEBUG, ("Buffering data until %d bytes or %d milliseconds (rate %f)\n",
                            mBufferingBytes - mStream->Available(),
-                           mBufferingWait - (now - mBufferingStart)));
+                           mBufferingWait - (PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart)),
+                           mStream->DownloadRate()));
         monitor.Wait(PR_MillisecondsToInterval(1000));
       } else {
         ChangeState(mNextState);
         nsCOMPtr<nsIRunnable> event =
           NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, BufferingStopped);
         NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
       }
 
       break;
-    }
 
-    case STATE_PLAYING: {
+    case STATE_PLAYING:
       if (!mAudioStream) {
         OpenAudioStream();
       } else {
         mAudioStream->Resume();
       }
 
-      if (mStream->Available() < mSampleSize) {
-        if (mExpectMoreData) {
-          // Buffer until mBufferingWait milliseconds of data is available.
-          mBufferingBytes = TimeToBytes(float(mBufferingWait) / 1000.0);
-          mBufferingStart = PR_IntervalNow();
-          ChangeState(STATE_BUFFERING);
-        } else {
-          // Media stream has ended and there is less data available than a
-          // single sample so end playback.
-          ChangeState(STATE_ENDED);
-        }
+      if (mStream->DownloadRate() >= 0 &&
+          mStream->Available() < mStream->PlaybackRate() * BUFFERING_SECONDS_LOW_WATER_MARK) {
+        nsCOMPtr<nsIRunnable> event =
+          NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, BufferingStarted);
+        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+
+        // Buffer until mBufferingWait milliseconds of data is available.
+        mBufferingBytes = TimeToBytes(float(mBufferingWait) / 1000.0);
+        mBufferingStart = PR_IntervalNow();
+        ChangeState(STATE_BUFFERING);
+      }
+
+      if (!mExpectMoreData && mStream->Available() < mSampleSize) {
+        // Media stream has ended and there is less data available than a
+        // single sample so end playback.
+        ChangeState(STATE_ENDED);
       } else {
         // Assuming enough data is available from the network, we aim to
         // completely fill the audio backend's buffers with data.  This
         // allows us plenty of time to wake up and refill the buffers
         // without an underrun occurring.
         PRUint32 sampleSize = mSampleFormat == nsAudioStream::FORMAT_U8 ? 1 : 2;
         PRUint32 len = RoundDownToSample(NS_MIN(mStream->Available(),
                                                 PRUint32(mAudioStream->Available() * sampleSize)));
@@ -630,17 +588,16 @@ nsWaveStateMachine::Run()
         // time to refill the buffers, causing an underrun.  To avoid this,
         // wake up when approximately half the buffered data has been
         // consumed.  This could be made smarter, but at least avoids waking
         // up frequently to perform small buffer refills.
         float nextWakeup = BytesToTime(mAudioBufferSize - mAudioStream->Available() * sizeof(short)) * 1000.0 / 2.0;
         monitor.Wait(PR_MillisecondsToInterval(PRUint32(nextWakeup)));
       }
       break;
-    }
 
     case STATE_SEEKING:
       {
         CloseAudioStream();
 
         mSeekTime = NS_MIN(mSeekTime, GetDuration());
         float seekTime = mSeekTime;
 
@@ -797,69 +754,16 @@ void
 nsWaveStateMachine::CloseAudioStream()
 {
   if (mAudioStream) {
     mAudioStream->Shutdown();
     mAudioStream = nsnull;
   }
 }
 
-nsMediaDecoder::Statistics
-nsWaveStateMachine::GetStatistics()
-{
-  nsMediaDecoder::Statistics result;
-  nsAutoMonitor monitor(mMonitor);
-  PRIntervalTime now = PR_IntervalNow();
-  result.mDownloadRate = mDownloadStatistics.GetRate(now, &result.mDownloadRateReliable);
-  result.mPlaybackRate = mSampleRate*mChannels*mSampleSize;
-  result.mPlaybackRateReliable = PR_TRUE;
-  result.mTotalBytes = mTotalBytes;
-  result.mDownloadPosition = mDownloadPosition;
-  result.mDecoderPosition = mPlaybackPosition;
-  result.mPlaybackPosition = mPlaybackPosition;
-  return result;
-}
-
-void
-nsWaveStateMachine::SetTotalBytes(PRInt64 aBytes) {
-  nsAutoMonitor monitor(mMonitor);
-  mTotalBytes = aBytes;
-}
-
-void
-nsWaveStateMachine::NotifyBytesDownloaded(PRInt64 aBytes)
-{
-  nsAutoMonitor monitor(mMonitor);
-  mDownloadStatistics.AddBytes(aBytes);
-  mDownloadPosition += aBytes;
-}
-
-void
-nsWaveStateMachine::NotifyDownloadSeeked(PRInt64 aOffset)
-{
-  nsAutoMonitor monitor(mMonitor);
-  mDownloadPosition = mPlaybackPosition = aOffset;
-}
-
-void
-nsWaveStateMachine::NotifyDownloadEnded(nsresult aStatus)
-{
-  if (aStatus == NS_BINDING_ABORTED)
-    return;
-  nsAutoMonitor monitor(mMonitor);
-  mDownloadStatistics.Stop(PR_IntervalNow());
-}
-
-void
-nsWaveStateMachine::NotifyBytesConsumed(PRInt64 aBytes)
-{
-  nsAutoMonitor monitor(mMonitor);
-  mPlaybackPosition += aBytes;
-}
-
 static PRUint32
 ReadUint32BE(const char** aBuffer)
 {
   PRUint32 result =
     PRUint8((*aBuffer)[0]) << 24 |
     PRUint8((*aBuffer)[1]) << 16 |
     PRUint8((*aBuffer)[2]) << 8 |
     PRUint8((*aBuffer)[3]);
@@ -1081,27 +985,26 @@ nsWaveStateMachine::FindDataOffset()
   mWaveLength = length;
   mWavePCMOffset = PRUint32(offset);
   return PR_TRUE;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver)
 
 nsWaveDecoder::nsWaveDecoder()
-  : mInitialVolume(1.0),
+  : mBytesDownloaded(0),
+    mInitialVolume(1.0),
     mStream(nsnull),
     mTimeOffset(0.0),
     mEndedCurrentTime(0.0),
     mEndedDuration(std::numeric_limits<float>::quiet_NaN()),
     mEnded(PR_FALSE),
     mNotifyOnShutdown(PR_FALSE),
     mSeekable(PR_TRUE),
-    mResourceLoaded(PR_FALSE),
-    mMetadataLoadedReported(PR_FALSE),
-    mResourceLoadedReported(PR_FALSE)
+    mResourceLoaded(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsWaveDecoder);
 }
 
 nsWaveDecoder::~nsWaveDecoder()
 {
   MOZ_COUNT_DTOR(nsWaveDecoder);
 }
@@ -1240,16 +1143,17 @@ nsWaveDecoder::Stop()
 }
 
 nsresult
 nsWaveDecoder::Load(nsIURI* aURI, nsIChannel* aChannel, nsIStreamListener** aStreamListener)
 {
   mStopping = PR_FALSE;
 
   // Reset progress member variables
+  mBytesDownloaded = 0;
   mResourceLoaded = PR_FALSE;
 
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
   if (aURI) {
     NS_ASSERTION(!aStreamListener, "No listener should be requested here");
@@ -1262,29 +1166,25 @@ nsWaveDecoder::Load(nsIURI* aURI, nsICha
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   RegisterShutdownObserver();
 
   mStream = new nsMediaStream();
   NS_ENSURE_TRUE(mStream, NS_ERROR_OUT_OF_MEMORY);
 
-  mPlaybackStateMachine = new nsWaveStateMachine(this, mStream.get(),
-                                                 BUFFERING_TIMEOUT * 1000,
-                                                 mInitialVolume);
-  NS_ENSURE_TRUE(mPlaybackStateMachine, NS_ERROR_OUT_OF_MEMORY);
-
-  // Open the stream *after* setting mPlaybackStateMachine, to ensure
-  // that callbacks (e.g. setting stream size) will actually work
   nsresult rv = mStream->Open(this, mURI, aChannel, aStreamListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = NS_NewThread(getter_AddRefs(mPlaybackThread));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mPlaybackStateMachine = new nsWaveStateMachine(this, mStream.get(),
+                                                 BUFFERING_TIMEOUT * 1000,
+                                                 mInitialVolume);
   rv = mPlaybackThread->Dispatch(mPlaybackStateMachine, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 void
 nsWaveDecoder::MetadataLoaded()
@@ -1293,23 +1193,25 @@ nsWaveDecoder::MetadataLoaded()
     return;
   }
 
   if (mElement) {
     mElement->MetadataLoaded();
     mElement->FirstFrameLoaded();
   }
 
-  mMetadataLoadedReported = PR_TRUE;
-
-  if (mResourceLoaded) {
-    ResourceLoaded();
-  } else {
+  if (!mResourceLoaded) {
     StartProgress();
   }
+  else if (mElement)
+  {
+    // Resource was loaded during metadata loading, when progress
+    // events are being ignored. Fire the final progress event.
+    mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
+  }
 }
 
 void
 nsWaveDecoder::PlaybackEnded()
 {
   if (mShuttingDown) {
     return;
   }
@@ -1321,48 +1223,51 @@ nsWaveDecoder::PlaybackEnded()
 }
 
 void
 nsWaveDecoder::ResourceLoaded()
 {
   if (mShuttingDown) {
     return;
   }
- 
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->StreamEnded(PR_TRUE);
+
+  // If we know the content length, set the bytes downloaded to this
+  // so the final progress event gets the correct final value.
+  if (mContentLength >= 0) {
+    mBytesDownloaded = mContentLength;
   }
 
   mResourceLoaded = PR_TRUE;
 
-  if (!mMetadataLoadedReported || mResourceLoadedReported)
-    return;
+  if (mElement) {
+    mElement->ResourceLoaded();
+  }
+  if (mPlaybackStateMachine) {
+    mPlaybackStateMachine->StreamEnded();
+  }
 
   StopProgress();
 
+  // Ensure the final progress event gets fired
   if (mElement) {
-    // Ensure the final progress event gets fired
     mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
-    mElement->ResourceLoaded();
   }
-
-  mResourceLoadedReported = PR_TRUE;
 }
 
 void
 nsWaveDecoder::NetworkError()
 {
   if (mShuttingDown) {
     return;
   }
   if (mElement) {
     mElement->NetworkError();
   }
   if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->StreamEnded(PR_FALSE);
+    mPlaybackStateMachine->StreamEnded();
   }
   Stop();
 }
 
 PRBool
 nsWaveDecoder::IsSeeking() const
 {
   if (mPlaybackStateMachine) {
@@ -1375,73 +1280,38 @@ PRBool
 nsWaveDecoder::IsEnded() const
 {
   if (mPlaybackStateMachine) {
     return mPlaybackStateMachine->IsEnded();
   }
   return mEnded;
 }
 
-nsMediaDecoder::Statistics
-nsWaveDecoder::GetStatistics()
+PRUint64
+nsWaveDecoder::GetBytesLoaded()
 {
-  if (!mPlaybackStateMachine)
-    return Statistics();
-  return mPlaybackStateMachine->GetStatistics();
-}
-
-void
-nsWaveDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
-{
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->NotifyBytesDownloaded(aBytes);
-  }
-  UpdateReadyStateForData();
+  return mBytesDownloaded;
 }
 
-void
-nsWaveDecoder::NotifyDownloadSeeked(PRInt64 aBytes)
-{
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->NotifyDownloadSeeked(aBytes);
-  }
-}
-
-void
-nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus)
+PRInt64
+nsWaveDecoder::GetTotalBytes()
 {
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->NotifyDownloadEnded(aStatus);
-  }
-  if (aStatus != NS_BINDING_ABORTED) {
-    if (NS_SUCCEEDED(aStatus)) {
-      ResourceLoaded();
-    } else if (aStatus != NS_BASE_STREAM_CLOSED) {
-      NetworkError();
-    }
-  }
-  UpdateReadyStateForData();
-}
-
-void
-nsWaveDecoder::NotifyBytesConsumed(PRInt64 aBytes)
-{
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->NotifyBytesConsumed(aBytes);
-  }
+  return mContentLength;
 }
 
 void
 nsWaveDecoder::SetTotalBytes(PRInt64 aBytes)
 {
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->SetTotalBytes(aBytes);
-  } else {
-    NS_WARNING("Forgot total bytes since there is no state machine set up");
-  }
+  mContentLength = aBytes;
+}
+
+void
+nsWaveDecoder::UpdateBytesDownloaded(PRUint64 aBytes)
+{
+  mBytesDownloaded = aBytes;
 }
 
 // An event that gets posted to the main thread, when the media element is
 // being destroyed, to destroy the decoder. Since the decoder shutdown can
 // block and post events this cannot be done inside destructor calls. So
 // this event is posted asynchronously to the main thread to perform the
 // shutdown. It keeps a strong reference to the decoder to ensure it does
 // not get deleted when the element is deleted.
@@ -1479,30 +1349,37 @@ nsWaveDecoder::Observe(nsISupports* aSub
 {
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
     Shutdown();
   }
   return NS_OK;
 }
 
 void
-nsWaveDecoder::UpdateReadyStateForData()
+nsWaveDecoder::BufferingStarted()
 {
-  if (!mElement || mShuttingDown || !mPlaybackStateMachine)
+  if (mShuttingDown) {
     return;
+  }
 
-  PRBool haveDataToPlay =
-    mPlaybackStateMachine->HasPendingData() && mMetadataLoadedReported;
-  mElement->UpdateReadyStateForData(haveDataToPlay);
+  if (mElement) {
+    mElement->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
+  }
 }
 
 void
 nsWaveDecoder::BufferingStopped()
 {
-  UpdateReadyStateForData();
+  if (mShuttingDown) {
+    return;
+  }
+
+  if (mElement) {
+    mElement->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
+  }
 }
 
 void
 nsWaveDecoder::SeekingStarted()
 {
   if (mShuttingDown) {
     return;
   }
@@ -1520,17 +1397,16 @@ void
 nsWaveDecoder::SeekingStopped()
 {
   if (mShuttingDown) {
     return;
   }
 
   if (mElement) {
     mElement->SeekCompleted();
-    UpdateReadyStateForData();
   }
 }
 
 void
 nsWaveDecoder::RegisterShutdownObserver()
 {
   if (!mNotifyOnShutdown) {
     nsCOMPtr<nsIObserverService> observerService =
--- a/content/media/video/test/Makefile.in
+++ b/content/media/video/test/Makefile.in
@@ -64,17 +64,16 @@ ifdef MOZ_OGG
 		file_access_controls.html \
 		test_bug448534.html \
 		test_bug461281.html \
 		test_can_play_type_ogg.html \
 		test_duration1.html \
 		test_ended1.html \
 		test_ended2.html \
 		test_onloadedmetadata.html \
-		test_progress1.html \
 		test_progress3.html \
 		test_standalone.html \
 		test_timeupdate1.html \
 		test_timeupdate2.html \
 		320x240.ogv \
 		test_videoDocumentTitle.html \
 		320x240.allow-origin.ogv \
 		320x240.allow-origin.ogv^headers^ \
@@ -92,16 +91,17 @@ ifneq ($(OS_ARCH),WINNT)
 		test_seek4.html \
 		test_seek5.html \
 		test_seek6.html \
 		test_seek7.html \
 		test_seek8.html \
 		test_timeupdate3.html \
 		$(NULL)
 endif
+#		test_progress1.html    disabled while we figure out the random failure
 else
 _TEST_FILES += \
 		test_can_play_type_no_ogg.html \
 		$(NULL)
 endif
 
 ifdef MOZ_WAVE
 _TEST_FILES += \
--- a/content/media/video/test/file_access_controls.html
+++ b/content/media/video/test/file_access_controls.html
@@ -59,18 +59,18 @@ var gTests = [
 var gTestNum = 0;
 var gExpectedResult = null;
 var gTestDescription = null;
 var video = null;
 var gTestedRemoved = false;
 var gOldPref;
 
 function result(code) {
-  dump((gTestNum - 1) + ": " + code + "\n");
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  dump("result " + code);
   opener.is(code, gExpectedResult, gTestDescription);
   nextTest();
 }
 
 function load() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   opener.is(window.location.href,
             "http://example.org/tests/content/media/video/test/file_access_controls.html",
@@ -100,19 +100,17 @@ function nextTest() {
     } else {
       // We're done, exit the test.
       window.close();
       return;
     }
   }
   gExpectedResult = gTests[gTestNum].result;
   gTestDescription = gTests[gTestNum].description;
-  dump("Starting test " + gTestNum + " at " + gTests[gTestNum].url + "\n");
   video.src = gTests[gTestNum].url;
-  video.load();
   gTestNum++;
 }
 
 function done() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   // Undo change to access control check pref.
   var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService);
--- a/content/media/video/test/test_media_selection.html
+++ b/content/media/video/test/test_media_selection.html
@@ -81,18 +81,17 @@ function late_add_sources_last(element, 
 
 function late_add_sources_first(element, name, type) {
   document.body.appendChild(element);
   do_add_source(element, name, type);
   do_add_source(element, name, 'unsupported/type');
 }
 
 function check_ogg(e) {
-  is(e.videoWidth, 320, "video width " + e.currentSrc);
-  is(e.videoHeight, 240, "video height " + e.currentSrc);
+  ok(e.videoWidth == 320 && e.videoHeight == 240, "video should be 320x240");
 }
 
 function check_wav(e) {
   ok(e.duration > 0.9 && e.duration < 1.1, "duration should be around 1.0");
 }
 
 var nextTest  = 0;
 var subTests = [
--- a/content/media/video/test/test_progress1.html
+++ b/content/media/video/test/test_progress1.html
@@ -7,42 +7,39 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var completed = false;
 var load_count = 0;
 var last_progress = false;
-var last_progress_total = 0;
 
 function on_loadedmetadata() {
   var v = document.getElementById('v');
-  ok(v, "Found video element after metadata loaded");
+  ok(v, "Found video element after metadata loaded: " + v);
   v.play();
   dump('test_progress1: on_loadedmetadata exiting\n');
 }
 
 function do_progress(e) {
   dump('test_progress1: do_progress ' + e.loaded + '\n');
-  ok(!completed, "Check for progress event after completed");
+  ok(!completed, "Check for progress event after completed: " + completed);
   ok(e.lengthComputable, "Check progress lengthComputable");
-  ok(e.loaded >= last_progress_total, "Check progress increasing: " + e.loaded);
-  last_progress_total = e.loaded;
-  ok(e.loaded <= e.total, "Check progress in bounds: " + e.loaded);
-  is(e.total, 285310, "Check progress total");
+  ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
+  ok(e.total == 285310, "Check progress total: " + e.total);
   last_progress = e.loaded;
 }
 
 function do_ended() {
   dump('test_progress1: do_ended\n');
   ok(!completed, "Check for duplicate ended event");
   completed = true;
-  is(last_progress, 285310, "Last progress event size");
-  is(load_count, 1, "load event raised");
+  ok(last_progress == 285310, "Last progress event size: " + last_progress);
+  ok(load_count == 1, "load event raised: " + load_count);
 
   SimpleTest.finish();
 }
 
 function do_load(e) {
   load_count++;
   dump('test_progress1: do_loaded ' + e.loaded + "\n");
 }
--- a/content/media/video/test/test_progress2.html
+++ b/content/media/video/test/test_progress2.html
@@ -7,37 +7,34 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 // Test progress events with wav backend
 var completed = false;
 var last_progress = false;
-var last_progress_total = 0;
 
 function on_loadedmetadata() {
   var v = document.getElementById('v');
   v.play();
 }
 
 function do_progress(e) {
-  ok(!completed, "Check for progress event after completed");
+  ok(!completed, "Check for progress event after completed: " + completed);
   ok(e.lengthComputable, "Check progress lengthComputable");
-  ok(e.loaded >= last_progress_total, "Check progress increasing: " + e.loaded);
-  last_progress_total = e.loaded;
-  ok(e.loaded <= e.total, "Check progress in bounds: " + e.loaded);
-  is(e.total, 102444, "Check progress total");
+  ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
+  ok(e.total == 102444, "Check progress total: " + e.total);
   last_progress = e.loaded;
 }
 
 function do_ended() {
   ok(!completed, "Check for duplicate ended event");
   completed = true;
-  is(last_progress, 102444, "Last progress event size");
+  ok(last_progress == 102444, "Last progress event size: " + last_progress);
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 <audio id='v'
        src='big.wav'
--- a/content/media/video/test/test_progress3.html
+++ b/content/media/video/test/test_progress3.html
@@ -9,57 +9,54 @@
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 // Same as test_progress1 but uses a smaller file to test resource loaded
 // before metadata loaded is fired.
 var completed = false;
 var load_count = 0;
 var last_progress = false;
-var last_progress_total = 0;
 
 function on_loadedmetadata() {
   var v = document.getElementById('v');
-  ok(v, "Found video element after metadata loaded");
+  ok(v, "Found video element after metadata loaded: " + v);
   v.play();
-  dump('test_progress3: on_loadedmetadata exiting\n');
+  dump('test_progress1: on_loadedmetadata exiting\n');
 }
 
 function do_progress(e) {
-  dump('test_progress3: do_progress ' + e.loaded + '/' + e.total + '\n');
-  ok(!completed, "Check for progress event after completed");
+  dump('test_progress1: do_progress ' + e.loaded + '\n');
+  ok(!completed, "Check for progress event after completed: " + completed);
   ok(e.lengthComputable, "Check progress lengthComputable");
-  ok(e.loaded >= last_progress_total, "Check progress increasing: " + e.loaded);
-  last_progress_total = e.loaded;
-  ok(e.loaded <= e.total, "Check progress in bounds: " + e.loaded);
-  is(e.total, 28942, "Check progress total");
+  ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
+  ok(e.total == 28942, "Check progress total: " + e.total);
   last_progress = e.loaded;
 }
 
 function do_ended() {
-  dump('test_progress3: do_ended\n');
+  dump('test_progress1: do_ended\n');
   ok(!completed, "Check for duplicate ended event");
   completed = true;
-  is(last_progress, 28942, "Last progress event size");
-  is(load_count, 1, "load event raised");
+  ok(last_progress == 28942, "Last progress event size: " + last_progress);
+  ok(load_count == 1, "load event raised: " + load_count);
   SimpleTest.finish();
 }
 
 function do_load(e) {
   load_count++;
-  dump('test_progress3: do_loaded ' + e.loaded + "\n");
+  dump('test_progress1: do_loaded ' + e.loaded + "\n");
 }
 
 function do_timeupdate() {
   var v = document.getElementById('v');
-  dump('test_progress3: timeupdate: ' + v.currentTime + "\n");
+  dump('test_progress1: timeupdate: ' + v.currentTime + "\n");
 }
 
 function do_play() {
-  dump('test_progress3: do_play\n');
+  dump('test_progress1: do_play\n');
 }
 
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 <video id='v'
        src='320x240.ogv'
        onloadedmetadata='on_loadedmetadata()'
--- a/content/media/video/test/test_progress4.html
+++ b/content/media/video/test/test_progress4.html
@@ -9,37 +9,34 @@
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 // Test progress events with wav backend
 // Same as test_progress2 but uses a smaller file to test resource loaded
 // before metadata loaded is fired.
 var completed = false;
 var last_progress = false;
-var last_progress_total = 0;
 
 function on_loadedmetadata() {
   var v = document.getElementById('v');
   v.play();
 }
 
 function do_progress(e) {
-  ok(!completed, "Check for progress event after completed");
+  ok(!completed, "Check for progress event after completed: " + completed);
   ok(e.lengthComputable, "Check progress lengthComputable");
-  ok(e.loaded >= last_progress_total, "Check progress increasing: " + e.loaded);
-  last_progress_total = e.loaded;
-  ok(e.loaded <= e.total, "Check progress in bounds: " + e.loaded);
-  is(e.total, 11069, "Check progress total");
+  ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
+  ok(e.total == 11069, "Check progress total: " + e.total);
   last_progress = e.loaded;
 }
 
 function do_ended() {
   ok(!completed, "Check for duplicate ended event");
   completed = true;
-  is(last_progress, 11069, "Last progress event size");
+  ok(last_progress == 11069, "Last progress event size: " + last_progress);
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 <audio id='v'
        src='r11025_u8_c1.wav'