--- 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'