Backed out changeset ba595db2b681

This commit is contained in:
Robert O'Callahan 2009-02-05 23:51:24 +13:00
parent 3c26c1d1a0
commit c752fcbe22
20 changed files with 469 additions and 885 deletions

View File

@ -111,6 +111,11 @@ public:
// 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();
@ -129,13 +134,6 @@ public:
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);

View File

@ -966,6 +966,8 @@ void nsHTMLMediaElement::MetadataLoaded()
void nsHTMLMediaElement::FirstFrameLoaded()
{
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
mLoadedFirstFrame = PR_TRUE;
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadeddata"));
}
void nsHTMLMediaElement::ResourceLoaded()
@ -993,6 +995,11 @@ void nsHTMLMediaElement::PlaybackEnded()
DispatchSimpleEvent(NS_LITERAL_STRING("ended"));
}
void nsHTMLMediaElement::CanPlayThrough()
{
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
}
void nsHTMLMediaElement::SeekStarted()
{
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
@ -1010,109 +1017,30 @@ PRBool nsHTMLMediaElement::ShouldCheckAllowOrigin()
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 && oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
if (mPlayingBeforeSeek && aState < 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)) {
@ -1123,10 +1051,6 @@ void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
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;
}
}
@ -1177,9 +1101,9 @@ nsresult nsHTMLMediaElement::DispatchProgressEvent(const nsAString& aName)
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;

View File

@ -71,6 +71,14 @@ public:
// 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();
@ -88,6 +96,7 @@ public:
public:
nsMediaStream mStream;
unsigned long mCurrentPosition;
};
#endif

View File

@ -44,6 +44,10 @@
#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;
/*
@ -67,12 +71,17 @@ class nsChannelToPipeListener : public nsIStreamListener
// 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:
@ -81,6 +90,22 @@ private:
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;
};

View File

@ -45,7 +45,6 @@
#include "gfxContext.h"
#include "gfxRect.h"
#include "nsITimer.h"
#include "prinrval.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gVideoDecoderLog;
@ -57,8 +56,7 @@ extern PRLogModuleInfo* gVideoDecoderLog;
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:
@ -140,39 +138,13 @@ class nsMediaDecoder : public nsIObserver
// 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;
@ -192,32 +164,8 @@ class nsMediaDecoder : public nsIObserver
// 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.
@ -256,70 +204,6 @@ protected:
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;

View File

@ -89,13 +89,12 @@ public:
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
@ -176,6 +175,15 @@ class nsMediaStream
// 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();
@ -197,6 +205,17 @@ class nsMediaStream
// 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

View File

@ -325,10 +325,7 @@ class nsOggDecoder : public nsMediaDecoder
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.
@ -358,8 +355,6 @@ class nsOggDecoder : public nsMediaDecoder
// 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.
@ -417,10 +412,22 @@ protected:
// 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();
@ -440,37 +447,11 @@ private:
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;
@ -497,6 +478,11 @@ private:
// 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.
@ -546,14 +532,13 @@ private:
// 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;

View File

@ -191,19 +191,18 @@ class nsWaveDecoder : public nsMediaDecoder
// 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();
@ -218,8 +217,8 @@ class nsWaveDecoder : public nsMediaDecoder
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();
@ -243,6 +242,12 @@ private:
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;
@ -283,12 +288,6 @@ private:
// 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

View File

@ -53,6 +53,16 @@ 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;
@ -81,6 +91,7 @@ size_t nsChannelReader::io_read(char* aBuffer, size_t aCount)
if (!NS_SUCCEEDED(rv)) {
return static_cast<size_t>(OGGZ_ERR_SYSTEM);
}
mCurrentPosition += bytes;
return bytes;
}
@ -136,6 +147,7 @@ nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI,
nsIChannel* aChannel,
nsIStreamListener** aStreamListener)
{
mCurrentPosition = 0;
return mStream.Open(aDecoder, aURI, aChannel, aStreamListener);
}

View File

@ -49,8 +49,13 @@
nsChannelToPipeListener::nsChannelToPipeListener(
nsMediaDecoder* aDecoder,
PRBool aSeeking) :
PRBool aSeeking,
PRInt64 aOffset) :
mDecoder(aDecoder),
mIntervalStart(0),
mIntervalEnd(0),
mOffset(aOffset),
mTotalBytes(0),
mSeeking(aSeeking)
{
}
@ -82,6 +87,11 @@ void nsChannelToPipeListener::Cancel()
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);
@ -103,11 +113,15 @@ nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISuppor
}
}
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;
@ -169,8 +183,12 @@ nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISuppor
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;
}
@ -184,16 +202,18 @@ nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest,
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);
@ -201,6 +221,8 @@ nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest,
// 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;
}

View File

@ -72,6 +72,7 @@ public:
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();
@ -200,6 +201,12 @@ PRUint32 nsDefaultStreamStrategy::Available()
return count;
}
float nsDefaultStreamStrategy::DownloadRate()
{
nsAutoLock lock(mLock);
return mListener ? mListener->BytesPerSecond() : NS_MEDIA_UNKNOWN_RATE;
}
void nsDefaultStreamStrategy::Cancel()
{
if (mListener)
@ -240,6 +247,7 @@ public:
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();
@ -257,36 +265,6 @@ private:
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) {
@ -332,6 +310,15 @@ nsresult nsFileStreamStrategy::Open(nsIStreamListener** aStreamListener)
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");
@ -343,21 +330,12 @@ nsresult nsFileStreamStrategy::Open(nsIStreamListener** aStreamListener)
}
}
// 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;
}
@ -377,35 +355,13 @@ nsresult nsFileStreamStrategy::Close()
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()
@ -430,6 +386,11 @@ PRUint32 nsFileStreamStrategy::Available()
return count;
}
float nsFileStreamStrategy::DownloadRate()
{
return NS_MEDIA_UNKNOWN_RATE;
}
nsIPrincipal* nsFileStreamStrategy::GetCurrentPrincipal()
{
return mPrincipal;
@ -464,6 +425,7 @@ public:
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();
@ -532,7 +494,7 @@ nsresult nsHttpStreamStrategy::OpenInternal(nsIStreamListener **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();
@ -582,11 +544,12 @@ nsresult nsHttpStreamStrategy::OpenInternal(nsIStreamListener **aStreamListener,
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");
@ -674,14 +637,15 @@ 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)
@ -693,7 +657,7 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
// 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;
@ -707,10 +671,12 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
// 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;
}
@ -751,7 +717,7 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
// 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(),
@ -762,13 +728,6 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
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;
}
}
@ -792,7 +751,7 @@ 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()
@ -809,6 +768,14 @@ PRUint32 nsHttpStreamStrategy::Available()
return count;
}
float nsHttpStreamStrategy::DownloadRate()
{
nsAutoLock lock(mLock);
if (!mListener)
return NS_MEDIA_UNKNOWN_RATE;
return mListener->BytesPerSecond();
}
void nsHttpStreamStrategy::Cancel()
{
mCancelled = PR_TRUE;
@ -839,7 +806,8 @@ void nsHttpStreamStrategy::Resume()
mChannel->Resume();
}
nsMediaStream::nsMediaStream()
nsMediaStream::nsMediaStream() :
mPlaybackRateCount(0)
{
NS_ASSERTION(NS_IsMainThread(),
"nsMediaStream created on non-main thread");
@ -879,6 +847,9 @@ nsresult nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
else
mStreamStrategy = new nsDefaultStreamStrategy(aDecoder, channel, aURI);
mPlaybackRateCount = 0;
mPlaybackRateStart = PR_IntervalNow();
return mStreamStrategy->Open(aListener);
}
@ -893,9 +864,7 @@ nsresult nsMediaStream::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;
}
@ -914,6 +883,18 @@ 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(),

View File

@ -151,8 +151,6 @@ public:
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;
@ -198,12 +196,12 @@ public:
return result;
}
PRBool IsEmpty() const
PRBool IsEmpty()
{
return mEmpty;
}
PRBool IsFull() const
PRBool IsFull()
{
return !mEmpty && mHead == mTail;
}
@ -300,12 +298,6 @@ public:
// 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
@ -362,9 +354,8 @@ private:
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
@ -412,17 +403,13 @@ private:
// 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.
@ -491,7 +478,6 @@ nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
mBufferingStart(0),
mBufferingBytes(0),
mLastFrameTime(0),
mLastFramePosition(-1),
mState(DECODER_STATE_DECODING_METADATA),
mSeekTime(0.0),
mCurrentFrameTime(0.0),
@ -530,18 +516,7 @@ nsOggDecodeStateMachine::FrameData* nsOggDecodeStateMachine::NextFrame()
}
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;
@ -673,7 +648,6 @@ void nsOggDecodeStateMachine::PlayFrame() {
PlayAudio(frame);
mDecodedFrames.Pop();
PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
UpdatePlaybackPosition(frame->mDecodedFrameTime);
delete frame;
}
@ -854,7 +828,6 @@ void nsOggDecodeStateMachine::Shutdown()
if (mPlayer) {
oggplay_prepare_for_close(mPlayer);
}
LOG(PR_LOG_DEBUG, ("Changed state to SHUTDOWN"));
mState = DECODER_STATE_SHUTDOWN;
mon.NotifyAll();
}
@ -865,7 +838,6 @@ void nsOggDecodeStateMachine::Decode()
// 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;
}
}
@ -874,7 +846,6 @@ 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;
}
@ -894,7 +865,6 @@ nsresult nsOggDecodeStateMachine::Run()
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;
@ -915,7 +885,6 @@ nsresult nsOggDecodeStateMachine::Run()
FrameData* frame = NextFrame();
if (frame) {
mDecodedFrames.Push(frame);
mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
UpdatePlaybackPosition(frame->mDecodedFrameTime);
PlayVideo(frame);
}
@ -925,7 +894,6 @@ nsresult nsOggDecodeStateMachine::Run()
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;
}
}
@ -933,61 +901,51 @@ nsresult nsOggDecodeStateMachine::Run()
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();
}
}
@ -1010,9 +968,7 @@ nsresult nsOggDecodeStateMachine::Run()
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
@ -1024,7 +980,6 @@ nsresult nsOggDecodeStateMachine::Run()
}
mon.Enter();
mLastFramePosition = mDecoder->mDecoderPosition;
mDecoder->StartProgressUpdates();
if (mState == DECODER_STATE_SHUTDOWN)
continue;
@ -1059,44 +1014,40 @@ nsresult nsOggDecodeStateMachine::Run()
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 &&
@ -1115,9 +1066,7 @@ nsresult nsOggDecodeStateMachine::Run()
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;
@ -1256,14 +1205,11 @@ float nsOggDecoder::GetDuration()
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),
@ -1282,7 +1228,7 @@ PRBool nsOggDecoder::Init(nsHTMLMediaElement* aElement)
return mMonitor && nsMediaDecoder::Init(aElement);
}
void nsOggDecoder::Shutdown()
void nsOggDecoder::Shutdown()
{
mShuttingDown = PR_TRUE;
@ -1306,10 +1252,7 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
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!");
@ -1338,8 +1281,6 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
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);
@ -1350,7 +1291,7 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
mDecodeStateMachine = new nsOggDecodeStateMachine(this);
{
nsAutoMonitor mon(mMonitor);
mDecodeStateMachine->SetContentLength(mTotalBytes);
mDecodeStateMachine->SetContentLength(mContentLength);
mDecodeStateMachine->SetSeekable(mSeekable);
}
@ -1446,7 +1387,6 @@ void nsOggDecoder::Stop()
ChangeState(PLAY_STATE_ENDED);
StopProgress();
mDownloadStatistics.Stop(PR_IntervalNow());
// Force any outstanding seek and byterange requests to complete
// to prevent shutdown from deadlocking.
@ -1568,7 +1508,7 @@ void nsOggDecoder::FirstFrameLoaded()
}
}
if (!mResourceLoaded && mDownloadPosition == mTotalBytes) {
if (!mResourceLoaded && mBytesDownloaded == mContentLength) {
ResourceLoaded();
}
}
@ -1582,23 +1522,28 @@ void nsOggDecoder::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);
// Note that mTotalBytes should not be -1 now; NotifyDownloadEnded
// should have set it to the download position.
NS_ASSERTION(mDownloadPosition == mTotalBytes, "Wrong byte count");
mResourceLoaded = PR_TRUE;
StopProgress();
}
Progress(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 (mContentLength >= 0) {
mBytesDownloaded = mContentLength;
}
mResourceLoaded = PR_TRUE;
StopProgress();
// Ensure the final progress event gets fired
if (mElement) {
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
@ -1608,7 +1553,7 @@ void nsOggDecoder::ResourceLoaded()
void nsOggDecoder::NetworkError()
{
if (mStopping || mShuttingDown)
if (mShuttingDown)
return;
if (mElement)
@ -1648,125 +1593,52 @@ NS_IMETHODIMP nsOggDecoder::Observe(nsISupports *aSubjet,
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);
}
}
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();
mDecodeStateMachine->SetContentLength(aBytes);
}
}
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;
mBytesDownloaded = aBytes;
}
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;
}
}
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()
@ -1787,7 +1659,6 @@ void nsOggDecoder::SeekingStopped()
if (mElement) {
mElement->SeekCompleted();
UpdateReadyStateForData();
}
}
@ -1932,7 +1803,6 @@ PRBool nsOggDecoder::GetSeekable()
void nsOggDecoder::Suspend()
{
mDownloadStatistics.Stop(PR_IntervalNow());
if (mReader) {
mReader->Suspend();
}
@ -1943,19 +1813,14 @@ 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());
}

View File

@ -149,8 +149,7 @@ public:
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();
@ -160,23 +159,6 @@ public:
// 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,
@ -303,19 +285,6 @@ private:
// 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;
@ -355,9 +324,6 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* a
mMonitor(nsnull),
mState(STATE_LOADING_METADATA),
mNextState(STATE_PAUSED),
mTotalBytes(-1),
mDownloadPosition(0),
mPlaybackPosition(0),
mInitialVolume(aInitialVolume),
mTimeOffset(0.0),
mExpectMoreData(PR_TRUE),
@ -365,7 +331,6 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* a
mMetadataValid(PR_FALSE)
{
mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
mDownloadStatistics.Start(PR_IntervalNow());
}
nsWaveStateMachine::~nsWaveStateMachine()
@ -443,11 +408,12 @@ 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);
}
@ -480,23 +446,10 @@ nsWaveStateMachine::IsEnded()
}
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
@ -538,13 +491,14 @@ nsWaveStateMachine::Run()
}
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);
@ -554,26 +508,30 @@ nsWaveStateMachine::Run()
}
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
@ -635,7 +593,6 @@ nsWaveStateMachine::Run()
monitor.Wait(PR_MillisecondsToInterval(PRUint32(nextWakeup)));
}
break;
}
case STATE_SEEKING:
{
@ -802,59 +759,6 @@ nsWaveStateMachine::CloseAudioStream()
}
}
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)
{
@ -1086,7 +990,8 @@ nsWaveStateMachine::FindDataOffset()
NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver)
nsWaveDecoder::nsWaveDecoder()
: mInitialVolume(1.0),
: mBytesDownloaded(0),
mInitialVolume(1.0),
mStream(nsnull),
mTimeOffset(0.0),
mEndedCurrentTime(0.0),
@ -1094,9 +999,7 @@ nsWaveDecoder::nsWaveDecoder()
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);
}
@ -1245,6 +1148,7 @@ nsWaveDecoder::Load(nsIURI* aURI, nsIChannel* aChannel, nsIStreamListener** aStr
mStopping = PR_FALSE;
// Reset progress member variables
mBytesDownloaded = 0;
mResourceLoaded = PR_FALSE;
if (aStreamListener) {
@ -1267,19 +1171,15 @@ nsWaveDecoder::Load(nsIURI* aURI, nsIChannel* aChannel, nsIStreamListener** aStr
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);
@ -1298,13 +1198,15 @@ nsWaveDecoder::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
@ -1326,25 +1228,28 @@ 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
@ -1357,7 +1262,7 @@ nsWaveDecoder::NetworkError()
mElement->NetworkError();
}
if (mPlaybackStateMachine) {
mPlaybackStateMachine->StreamEnded(PR_FALSE);
mPlaybackStateMachine->StreamEnded();
}
Stop();
}
@ -1380,63 +1285,28 @@ nsWaveDecoder::IsEnded() const
return mEnded;
}
nsMediaDecoder::Statistics
nsWaveDecoder::GetStatistics()
PRUint64
nsWaveDecoder::GetBytesLoaded()
{
if (!mPlaybackStateMachine)
return Statistics();
return mPlaybackStateMachine->GetStatistics();
return mBytesDownloaded;
}
void
nsWaveDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
PRInt64
nsWaveDecoder::GetTotalBytes()
{
if (mPlaybackStateMachine) {
mPlaybackStateMachine->NotifyBytesDownloaded(aBytes);
}
UpdateReadyStateForData();
}
void
nsWaveDecoder::NotifyDownloadSeeked(PRInt64 aBytes)
{
if (mPlaybackStateMachine) {
mPlaybackStateMachine->NotifyDownloadSeeked(aBytes);
}
}
void
nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus)
{
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
@ -1484,20 +1354,27 @@ nsWaveDecoder::Observe(nsISupports* aSubject, const char* aTopic, const PRUnicha
}
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
@ -1525,7 +1402,6 @@ nsWaveDecoder::SeekingStopped()
if (mElement) {
mElement->SeekCompleted();
UpdateReadyStateForData();
}
}

View File

@ -69,7 +69,6 @@ _TEST_FILES += \
test_ended1.html \
test_ended2.html \
test_onloadedmetadata.html \
test_progress1.html \
test_progress3.html \
test_standalone.html \
test_timeupdate1.html \
@ -97,6 +96,7 @@ _TEST_FILES += \
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 \

View File

@ -64,8 +64,8 @@ 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();
}
@ -105,9 +105,7 @@ function nextTest() {
}
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++;
}

View File

@ -86,8 +86,7 @@ function late_add_sources_first(element, name, 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) {

View File

@ -12,23 +12,20 @@
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;
}
@ -36,8 +33,8 @@ 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();
}

View File

@ -12,7 +12,6 @@
// 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');
@ -20,19 +19,17 @@ function on_loadedmetadata() {
}
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();
}

View File

@ -14,47 +14,44 @@
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();

View File

@ -14,7 +14,6 @@
// 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');
@ -22,19 +21,17 @@ function on_loadedmetadata() {
}
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();
}