зеркало из https://github.com/mozilla/gecko-dev.git
Bug 455654. Rework the download-rate, playback-rate and related statistics so they're managed by the decoders in a thread-safe way. Implement proper HTML5 readyState handling with event firing, including 'canplay' and 'canplaythrough' events and autoplay behaviour. Also changes buffering so that we buffer when we're actually about to run out of data, instead of trying to guess when to buffer based on rate estimates. r=doublec
This commit is contained in:
Родитель
5bae18289f
Коммит
f0ff9de335
|
@ -111,11 +111,6 @@ 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();
|
||||
|
@ -134,6 +129,13 @@ 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);
|
||||
|
|
|
@ -966,8 +966,6 @@ void nsHTMLMediaElement::MetadataLoaded()
|
|||
void nsHTMLMediaElement::FirstFrameLoaded()
|
||||
{
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
|
||||
mLoadedFirstFrame = PR_TRUE;
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadeddata"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::ResourceLoaded()
|
||||
|
@ -995,11 +993,6 @@ void nsHTMLMediaElement::PlaybackEnded()
|
|||
DispatchSimpleEvent(NS_LITERAL_STRING("ended"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::CanPlayThrough()
|
||||
{
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::SeekStarted()
|
||||
{
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
|
||||
|
@ -1017,30 +1010,109 @@ 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)
|
||||
{
|
||||
// Handle raising of "waiting" event during seek (see 4.8.10.9)
|
||||
if (mPlayingBeforeSeek && aState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA)
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
|
||||
nsMediaReadyState oldState = mReadyState;
|
||||
|
||||
// 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:
|
||||
LOG(PR_LOG_DEBUG, ("Ready state changed to 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"));
|
||||
}
|
||||
break;
|
||||
|
||||
case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
|
||||
LOG(PR_LOG_DEBUG, ("Ready state changed to 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;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
|
||||
LOG(PR_LOG_DEBUG, ("Ready state changed to 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"));
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
|
@ -1051,6 +1123,10 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -1101,9 +1177,9 @@ nsresult nsHTMLMediaElement::DispatchProgressEvent(const nsAString& aName)
|
|||
nsCOMPtr<nsIDOMProgressEvent> progressEvent(do_QueryInterface(event));
|
||||
NS_ENSURE_TRUE(progressEvent, NS_ERROR_FAILURE);
|
||||
|
||||
PRInt64 length = mDecoder->GetTotalBytes();
|
||||
nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
|
||||
rv = progressEvent->InitProgressEvent(aName, PR_TRUE, PR_TRUE,
|
||||
length >= 0, mDecoder->GetBytesLoaded(), length);
|
||||
stats.mTotalBytes >= 0, stats.mDownloadPosition, stats.mTotalBytes);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
PRBool dummy;
|
||||
|
|
|
@ -71,14 +71,6 @@ 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();
|
||||
|
||||
|
@ -96,7 +88,6 @@ public:
|
|||
|
||||
public:
|
||||
nsMediaStream mStream;
|
||||
unsigned long mCurrentPosition;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -44,10 +44,6 @@
|
|||
#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;
|
||||
|
||||
/*
|
||||
|
@ -71,17 +67,12 @@ 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,
|
||||
PRInt64 aOffset = 0);
|
||||
PRBool aSeeking = PR_FALSE);
|
||||
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:
|
||||
|
@ -90,22 +81,6 @@ 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;
|
||||
};
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "gfxContext.h"
|
||||
#include "gfxRect.h"
|
||||
#include "nsITimer.h"
|
||||
#include "prinrval.h"
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gVideoDecoderLog;
|
||||
|
@ -56,7 +57,8 @@ extern PRLogModuleInfo* gVideoDecoderLog;
|
|||
class nsHTMLMediaElement;
|
||||
|
||||
// All methods of nsMediaDecoder must be called from the main thread only
|
||||
// with the exception of SetRGBData. The latter can be called from any thread.
|
||||
// with the exception of SetRGBData and GetStatistics, which can be
|
||||
// called from any thread.
|
||||
class nsMediaDecoder : public nsIObserver
|
||||
{
|
||||
public:
|
||||
|
@ -138,13 +140,39 @@ class nsMediaDecoder : public nsIObserver
|
|||
// Call in the main thread only.
|
||||
virtual PRBool IsEnded() const = 0;
|
||||
|
||||
// Return the current number of bytes loaded from the video file.
|
||||
// This is used for progress events.
|
||||
virtual PRUint64 GetBytesLoaded() = 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 size of the video file in bytes. Return 0 if the
|
||||
// size is unknown or the stream is infinite.
|
||||
virtual PRInt64 GetTotalBytes() = 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;
|
||||
|
||||
// Set the size of the video file in bytes.
|
||||
virtual void SetTotalBytes(PRInt64 aBytes) = 0;
|
||||
|
@ -164,8 +192,32 @@ class nsMediaDecoder : public nsIObserver
|
|||
// than the result of downloaded data.
|
||||
virtual void Progress(PRBool aTimer);
|
||||
|
||||
// Keep track of the number of bytes downloaded
|
||||
virtual void UpdateBytesDownloaded(PRUint64 aBytes) = 0;
|
||||
// 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;
|
||||
|
||||
// Cleanup internal data structures. Must be called on the main
|
||||
// thread by the owning object before that object disposes of this object.
|
||||
|
@ -204,6 +256,70 @@ 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;
|
||||
|
|
|
@ -89,12 +89,13 @@ 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
|
||||
|
@ -175,15 +176,6 @@ 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();
|
||||
|
@ -205,17 +197,6 @@ 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
|
||||
|
|
|
@ -325,7 +325,10 @@ class nsOggDecoder : public nsMediaDecoder
|
|||
virtual void GetCurrentURI(nsIURI** aURI);
|
||||
virtual nsIPrincipal* GetCurrentPrincipal();
|
||||
|
||||
virtual void UpdateBytesDownloaded(PRUint64 aBytes);
|
||||
virtual void NotifyBytesDownloaded(PRInt64 aBytes);
|
||||
virtual void NotifyDownloadSeeked(PRInt64 aOffsetBytes);
|
||||
virtual void NotifyDownloadEnded(nsresult aStatus);
|
||||
virtual void NotifyBytesConsumed(PRInt64 aBytes);
|
||||
|
||||
// Called when the video file has completed downloading.
|
||||
// Call on the main thread only.
|
||||
|
@ -355,6 +358,8 @@ 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.
|
||||
|
@ -412,22 +417,10 @@ 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();
|
||||
|
@ -447,11 +440,37 @@ 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 on the main thread only
|
||||
* The following members should be accessed with the decoder lock held.
|
||||
******/
|
||||
// Total number of bytes downloaded so far.
|
||||
PRUint64 mBytesDownloaded;
|
||||
|
||||
// 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;
|
||||
|
||||
// The URI of the current resource
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
|
@ -478,11 +497,6 @@ 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.
|
||||
|
@ -532,13 +546,14 @@ private:
|
|||
// Any change to the state must call NotifyAll on the monitor.
|
||||
PlayState mNextState;
|
||||
|
||||
// True when the media resource has completely loaded. Accessed on
|
||||
// the main thread only.
|
||||
// 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.
|
||||
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
|
||||
// before a seek or during loading of metadata to prevent the progress indicator
|
||||
// during seek and duration operations to prevent the progress indicator
|
||||
// from jumping around. Read/Write from any thread. Must have decode monitor
|
||||
// locked before accessing.
|
||||
PRPackedBool mIgnoreProgressData;
|
||||
|
|
|
@ -191,18 +191,19 @@ class nsWaveDecoder : public nsMediaDecoder
|
|||
// Element is notifying us that the requested playback rate has changed.
|
||||
virtual nsresult PlaybackRateChanged();
|
||||
|
||||
// Getter/setter for mContentLength.
|
||||
virtual PRInt64 GetTotalBytes();
|
||||
virtual void NotifyBytesDownloaded(PRInt64 aBytes);
|
||||
virtual void NotifyDownloadSeeked(PRInt64 aOffset);
|
||||
virtual void NotifyDownloadEnded(nsresult aStatus);
|
||||
virtual void NotifyBytesConsumed(PRInt64 aBytes);
|
||||
|
||||
virtual Statistics GetStatistics();
|
||||
|
||||
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();
|
||||
|
||||
|
@ -217,8 +218,8 @@ class nsWaveDecoder : public nsMediaDecoder
|
|||
virtual void Resume();
|
||||
|
||||
private:
|
||||
// Notifies the nsHTMLMediaElement that buffering has started.
|
||||
void BufferingStarted();
|
||||
// Change the element's ready state as necessary
|
||||
void UpdateReadyStateForData();
|
||||
|
||||
// Notifies the element that buffering has stopped.
|
||||
void BufferingStopped();
|
||||
|
@ -242,12 +243,6 @@ 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;
|
||||
|
||||
|
@ -288,6 +283,12 @@ 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
|
||||
|
|
|
@ -53,16 +53,6 @@ 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;
|
||||
|
@ -91,7 +81,6 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -147,7 +136,6 @@ nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI,
|
|||
nsIChannel* aChannel,
|
||||
nsIStreamListener** aStreamListener)
|
||||
{
|
||||
mCurrentPosition = 0;
|
||||
return mStream.Open(aDecoder, aURI, aChannel, aStreamListener);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,13 +49,8 @@
|
|||
|
||||
nsChannelToPipeListener::nsChannelToPipeListener(
|
||||
nsMediaDecoder* aDecoder,
|
||||
PRBool aSeeking,
|
||||
PRInt64 aOffset) :
|
||||
PRBool aSeeking) :
|
||||
mDecoder(aDecoder),
|
||||
mIntervalStart(0),
|
||||
mIntervalEnd(0),
|
||||
mOffset(aOffset),
|
||||
mTotalBytes(0),
|
||||
mSeeking(aSeeking)
|
||||
{
|
||||
}
|
||||
|
@ -87,11 +82,6 @@ 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);
|
||||
|
@ -113,15 +103,11 @@ 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;
|
||||
nsresult rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
|
||||
ranges);
|
||||
hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
|
||||
ranges);
|
||||
PRBool acceptsRanges = ranges.EqualsLiteral("bytes");
|
||||
|
||||
PRUint32 responseStatus = 0;
|
||||
|
@ -183,12 +169,8 @@ nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISuppor
|
|||
nsresult nsChannelToPipeListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
|
||||
{
|
||||
mOutput = nsnull;
|
||||
if (aStatus != NS_BINDING_ABORTED && mDecoder) {
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
mDecoder->ResourceLoaded();
|
||||
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
|
||||
mDecoder->NetworkError();
|
||||
}
|
||||
if (mDecoder) {
|
||||
mDecoder->NotifyDownloadEnded(aStatus);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -202,18 +184,16 @@ nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest,
|
|||
if (!mOutput)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
PRUint32 bytes = 0;
|
||||
|
||||
mDecoder->NotifyBytesDownloaded(aCount);
|
||||
|
||||
do {
|
||||
PRUint32 bytes;
|
||||
nsresult rv = mOutput->WriteFrom(aStream, aCount, &bytes);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
aCount -= bytes;
|
||||
mTotalBytes += bytes;
|
||||
aOffset += bytes;
|
||||
mDecoder->UpdateBytesDownloaded(mOffset + aOffset);
|
||||
} while (aCount) ;
|
||||
} while (aCount);
|
||||
|
||||
nsresult rv = mOutput->Flush();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -221,8 +201,6 @@ 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@ 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();
|
||||
|
@ -201,12 +200,6 @@ PRUint32 nsDefaultStreamStrategy::Available()
|
|||
return count;
|
||||
}
|
||||
|
||||
float nsDefaultStreamStrategy::DownloadRate()
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
return mListener ? mListener->BytesPerSecond() : NS_MEDIA_UNKNOWN_RATE;
|
||||
}
|
||||
|
||||
void nsDefaultStreamStrategy::Cancel()
|
||||
{
|
||||
if (mListener)
|
||||
|
@ -247,7 +240,6 @@ 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();
|
||||
|
@ -265,6 +257,36 @@ 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) {
|
||||
|
@ -310,15 +332,6 @@ 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");
|
||||
|
@ -330,12 +343,21 @@ nsresult nsFileStreamStrategy::Open(nsIStreamListener** aStreamListener)
|
|||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -355,13 +377,35 @@ nsresult nsFileStreamStrategy::Close()
|
|||
nsresult nsFileStreamStrategy::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
return mInput ? mInput->Read(aBuffer, aCount, aBytes) : NS_ERROR_FAILURE;
|
||||
if (!mInput)
|
||||
return NS_ERROR_FAILURE;
|
||||
return mInput->Read(aBuffer, aCount, aBytes);
|
||||
}
|
||||
|
||||
nsresult nsFileStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
return mSeekable ? mSeekable->Seek(aWhence, aOffset) : NS_ERROR_FAILURE;
|
||||
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;
|
||||
}
|
||||
|
||||
PRInt64 nsFileStreamStrategy::Tell()
|
||||
|
@ -386,11 +430,6 @@ PRUint32 nsFileStreamStrategy::Available()
|
|||
return count;
|
||||
}
|
||||
|
||||
float nsFileStreamStrategy::DownloadRate()
|
||||
{
|
||||
return NS_MEDIA_UNKNOWN_RATE;
|
||||
}
|
||||
|
||||
nsIPrincipal* nsFileStreamStrategy::GetCurrentPrincipal()
|
||||
{
|
||||
return mPrincipal;
|
||||
|
@ -425,7 +464,6 @@ 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();
|
||||
|
@ -494,7 +532,7 @@ nsresult nsHttpStreamStrategy::OpenInternal(nsIStreamListener **aStreamListener,
|
|||
*aStreamListener = nsnull;
|
||||
}
|
||||
|
||||
mListener = new nsChannelToPipeListener(mDecoder, aOffset != 0, aOffset);
|
||||
mListener = new nsChannelToPipeListener(mDecoder, aOffset != 0);
|
||||
NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
nsresult rv = mListener->Init();
|
||||
|
@ -544,12 +582,11 @@ nsresult nsHttpStreamStrategy::OpenInternal(nsIStreamListener **aStreamListener,
|
|||
rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mPosition = aOffset;
|
||||
mDecoder->NotifyDownloadSeeked(aOffset);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult nsHttpStreamStrategy::Close()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
|
@ -637,15 +674,14 @@ 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)
|
||||
|
@ -657,7 +693,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 (mDecoder->GetTotalBytes() == -1)
|
||||
if (totalBytes == -1)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
mAtEOF = PR_TRUE;
|
||||
|
@ -671,12 +707,10 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
|
|||
// NS_SEEK_SET
|
||||
switch (aWhence) {
|
||||
case nsISeekableStream::NS_SEEK_END: {
|
||||
PRInt32 length;
|
||||
mChannel->GetContentLength(&length);
|
||||
if (length == -1)
|
||||
if (totalBytes == -1)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
aOffset -= length;
|
||||
aOffset += totalBytes;
|
||||
aWhence = nsISeekableStream::NS_SEEK_SET;
|
||||
break;
|
||||
}
|
||||
|
@ -717,7 +751,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.
|
||||
PRUint32 bytesRead = 0;
|
||||
PRInt32 bytesRead = 0;
|
||||
PRUint32 bytes = 0;
|
||||
do {
|
||||
nsresult rv = mPipeInput->Read(data.get(),
|
||||
|
@ -728,6 +762,13 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -751,7 +792,7 @@ PRInt64 nsHttpStreamStrategy::Tell()
|
|||
{
|
||||
// Handle the case of a seek to EOF by liboggz
|
||||
// (See Seek for details)
|
||||
return mAtEOF ? mDecoder->GetTotalBytes() : mPosition;
|
||||
return mAtEOF ? mDecoder->GetStatistics().mTotalBytes : mPosition;
|
||||
}
|
||||
|
||||
PRUint32 nsHttpStreamStrategy::Available()
|
||||
|
@ -768,14 +809,6 @@ 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;
|
||||
|
@ -806,8 +839,7 @@ void nsHttpStreamStrategy::Resume()
|
|||
mChannel->Resume();
|
||||
}
|
||||
|
||||
nsMediaStream::nsMediaStream() :
|
||||
mPlaybackRateCount(0)
|
||||
nsMediaStream::nsMediaStream()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(),
|
||||
"nsMediaStream created on non-main thread");
|
||||
|
@ -847,9 +879,6 @@ nsresult nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
|
|||
else
|
||||
mStreamStrategy = new nsDefaultStreamStrategy(aDecoder, channel, aURI);
|
||||
|
||||
mPlaybackRateCount = 0;
|
||||
mPlaybackRateStart = PR_IntervalNow();
|
||||
|
||||
return mStreamStrategy->Open(aListener);
|
||||
}
|
||||
|
||||
|
@ -864,7 +893,9 @@ nsresult nsMediaStream::Close()
|
|||
nsresult nsMediaStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
|
||||
{
|
||||
nsresult rv = mStreamStrategy->Read(aBuffer, aCount, aBytes);
|
||||
mPlaybackRateCount += *aBytes;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mStreamStrategy->Decoder()->NotifyBytesConsumed(*aBytes);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -883,18 +914,6 @@ 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(),
|
||||
|
|
|
@ -151,6 +151,8 @@ 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;
|
||||
|
@ -196,12 +198,12 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
PRBool IsEmpty()
|
||||
PRBool IsEmpty() const
|
||||
{
|
||||
return mEmpty;
|
||||
}
|
||||
|
||||
PRBool IsFull()
|
||||
PRBool IsFull() const
|
||||
{
|
||||
return !mEmpty && mHead == mTail;
|
||||
}
|
||||
|
@ -298,6 +300,12 @@ 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
|
||||
|
@ -354,8 +362,9 @@ private:
|
|||
OggPlay* mPlayer;
|
||||
|
||||
// Frame data containing decoded video/audio for the frame the
|
||||
// current frame and the previous frame. Accessed only via the
|
||||
// decoder thread.
|
||||
// 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.
|
||||
FrameQueue mDecodedFrames;
|
||||
|
||||
// The time that playback started from the system clock. This is used
|
||||
|
@ -403,13 +412,17 @@ private:
|
|||
|
||||
// Number of bytes to buffer when buffering. Only accessed in the
|
||||
// decoder thread.
|
||||
PRUint32 mBufferingBytes;
|
||||
PRInt64 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.
|
||||
|
@ -478,6 +491,7 @@ nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
|
|||
mBufferingStart(0),
|
||||
mBufferingBytes(0),
|
||||
mLastFrameTime(0),
|
||||
mLastFramePosition(-1),
|
||||
mState(DECODER_STATE_DECODING_METADATA),
|
||||
mSeekTime(0.0),
|
||||
mCurrentFrameTime(0.0),
|
||||
|
@ -516,7 +530,18 @@ 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;
|
||||
|
@ -648,6 +673,7 @@ void nsOggDecodeStateMachine::PlayFrame() {
|
|||
PlayAudio(frame);
|
||||
mDecodedFrames.Pop();
|
||||
PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
|
||||
mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
|
||||
UpdatePlaybackPosition(frame->mDecodedFrameTime);
|
||||
delete frame;
|
||||
}
|
||||
|
@ -828,6 +854,7 @@ void nsOggDecodeStateMachine::Shutdown()
|
|||
if (mPlayer) {
|
||||
oggplay_prepare_for_close(mPlayer);
|
||||
}
|
||||
LOG(PR_LOG_DEBUG, ("Changed state to SHUTDOWN"));
|
||||
mState = DECODER_STATE_SHUTDOWN;
|
||||
mon.NotifyAll();
|
||||
}
|
||||
|
@ -838,6 +865,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -846,6 +874,7 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -865,6 +894,7 @@ 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;
|
||||
|
@ -885,6 +915,7 @@ nsresult nsOggDecodeStateMachine::Run()
|
|||
FrameData* frame = NextFrame();
|
||||
if (frame) {
|
||||
mDecodedFrames.Push(frame);
|
||||
mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
|
||||
UpdatePlaybackPosition(frame->mDecodedFrameTime);
|
||||
PlayVideo(frame);
|
||||
}
|
||||
|
@ -894,6 +925,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -901,51 +933,61 @@ nsresult nsOggDecodeStateMachine::Run()
|
|||
|
||||
case DECODER_STATE_DECODING:
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
mBufferingStart = PR_IntervalNow();
|
||||
mBufferingBytes = PRUint32(BUFFERING_RATE(reader->PlaybackRate()) * BUFFERING_WAIT);
|
||||
double playbackRate = mDecoder->GetStatistics().mPlaybackRate;
|
||||
mBufferingBytes = BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
|
||||
mState = DECODER_STATE_BUFFERING;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING (%d bytes)", PRInt32(mBufferingBytes)));
|
||||
} else {
|
||||
PlayFrame();
|
||||
}
|
||||
}
|
||||
|
@ -968,7 +1010,9 @@ 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
|
||||
|
@ -980,6 +1024,7 @@ nsresult nsOggDecodeStateMachine::Run()
|
|||
}
|
||||
|
||||
mon.Enter();
|
||||
mLastFramePosition = mDecoder->mDecoderPosition;
|
||||
mDecoder->StartProgressUpdates();
|
||||
if (mState == DECODER_STATE_SHUTDOWN)
|
||||
continue;
|
||||
|
@ -1014,39 +1059,43 @@ 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:
|
||||
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;
|
||||
}
|
||||
{
|
||||
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 (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:
|
||||
{
|
||||
|
@ -1066,7 +1115,9 @@ 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;
|
||||
|
@ -1205,11 +1256,14 @@ float nsOggDecoder::GetDuration()
|
|||
|
||||
nsOggDecoder::nsOggDecoder() :
|
||||
nsMediaDecoder(),
|
||||
mBytesDownloaded(0),
|
||||
mTotalBytes(-1),
|
||||
mDownloadPosition(0),
|
||||
mProgressPosition(0),
|
||||
mDecoderPosition(0),
|
||||
mPlaybackPosition(0),
|
||||
mCurrentTime(0.0),
|
||||
mInitialVolume(0.0),
|
||||
mRequestedSeekTime(-1.0),
|
||||
mContentLength(-1),
|
||||
mNotifyOnShutdown(PR_FALSE),
|
||||
mSeekable(PR_TRUE),
|
||||
mReader(0),
|
||||
|
@ -1228,7 +1282,7 @@ PRBool nsOggDecoder::Init(nsHTMLMediaElement* aElement)
|
|||
return mMonitor && nsMediaDecoder::Init(aElement);
|
||||
}
|
||||
|
||||
void nsOggDecoder::Shutdown()
|
||||
void nsOggDecoder::Shutdown()
|
||||
{
|
||||
mShuttingDown = PR_TRUE;
|
||||
|
||||
|
@ -1252,7 +1306,10 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
|
|||
mStopping = PR_FALSE;
|
||||
|
||||
// Reset progress member variables
|
||||
mBytesDownloaded = 0;
|
||||
mDownloadPosition = 0;
|
||||
mProgressPosition = 0;
|
||||
mDecoderPosition = 0;
|
||||
mPlaybackPosition = 0;
|
||||
mResourceLoaded = PR_FALSE;
|
||||
|
||||
NS_ASSERTION(!mReader, "Didn't shutdown properly!");
|
||||
|
@ -1281,6 +1338,8 @@ 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);
|
||||
|
@ -1291,7 +1350,7 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
|
|||
mDecodeStateMachine = new nsOggDecodeStateMachine(this);
|
||||
{
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
mDecodeStateMachine->SetContentLength(mContentLength);
|
||||
mDecodeStateMachine->SetContentLength(mTotalBytes);
|
||||
mDecodeStateMachine->SetSeekable(mSeekable);
|
||||
}
|
||||
|
||||
|
@ -1387,6 +1446,7 @@ 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.
|
||||
|
@ -1508,7 +1568,7 @@ void nsOggDecoder::FirstFrameLoaded()
|
|||
}
|
||||
}
|
||||
|
||||
if (!mResourceLoaded && mBytesDownloaded == mContentLength) {
|
||||
if (!mResourceLoaded && mDownloadPosition == mTotalBytes) {
|
||||
ResourceLoaded();
|
||||
}
|
||||
}
|
||||
|
@ -1522,28 +1582,23 @@ 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);
|
||||
ignoreProgress = mIgnoreProgressData;
|
||||
if (ignoreProgress || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
|
||||
if (mIgnoreProgressData || 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"));
|
||||
|
@ -1553,7 +1608,7 @@ void nsOggDecoder::ResourceLoaded()
|
|||
|
||||
void nsOggDecoder::NetworkError()
|
||||
{
|
||||
if (mShuttingDown)
|
||||
if (mStopping || mShuttingDown)
|
||||
return;
|
||||
|
||||
if (mElement)
|
||||
|
@ -1593,52 +1648,125 @@ NS_IMETHODIMP nsOggDecoder::Observe(nsISupports *aSubjet,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
PRUint64 nsOggDecoder::GetBytesLoaded()
|
||||
nsMediaDecoder::Statistics
|
||||
nsOggDecoder::GetStatistics()
|
||||
{
|
||||
return mBytesDownloaded;
|
||||
}
|
||||
Statistics result;
|
||||
|
||||
PRInt64 nsOggDecoder::GetTotalBytes()
|
||||
{
|
||||
return mContentLength;
|
||||
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;
|
||||
}
|
||||
|
||||
void nsOggDecoder::SetTotalBytes(PRInt64 aBytes)
|
||||
{
|
||||
mContentLength = aBytes;
|
||||
if (mDecodeStateMachine) {
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
mDecodeStateMachine->SetContentLength(aBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void nsOggDecoder::UpdateBytesDownloaded(PRUint64 aBytes)
|
||||
{
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
|
||||
if (!mIgnoreProgressData) {
|
||||
mBytesDownloaded = aBytes;
|
||||
// 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);
|
||||
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();
|
||||
}
|
||||
|
||||
void nsOggDecoder::NotifyDownloadSeeked(PRInt64 aOffsetBytes)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
UpdateReadyStateForData();
|
||||
}
|
||||
|
||||
void nsOggDecoder::SeekingStopped()
|
||||
|
@ -1659,6 +1787,7 @@ void nsOggDecoder::SeekingStopped()
|
|||
|
||||
if (mElement) {
|
||||
mElement->SeekCompleted();
|
||||
UpdateReadyStateForData();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1803,6 +1932,7 @@ PRBool nsOggDecoder::GetSeekable()
|
|||
|
||||
void nsOggDecoder::Suspend()
|
||||
{
|
||||
mDownloadStatistics.Stop(PR_IntervalNow());
|
||||
if (mReader) {
|
||||
mReader->Suspend();
|
||||
}
|
||||
|
@ -1813,14 +1943,19 @@ 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());
|
||||
}
|
||||
|
|
|
@ -149,7 +149,8 @@ public:
|
|||
PRBool IsEnded();
|
||||
|
||||
// Called by the decoder to indicate that the media stream has closed.
|
||||
void StreamEnded();
|
||||
// aAtEnd is true if we read to the end of the file.
|
||||
void StreamEnded(PRBool aAtEnd);
|
||||
|
||||
// Main state machine loop. Runs forever, until shutdown state is reached.
|
||||
NS_IMETHOD Run();
|
||||
|
@ -159,6 +160,23 @@ 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,
|
||||
|
@ -285,6 +303,19 @@ 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;
|
||||
|
||||
|
@ -324,6 +355,9 @@ 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),
|
||||
|
@ -331,6 +365,7 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* a
|
|||
mMetadataValid(PR_FALSE)
|
||||
{
|
||||
mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
|
||||
mDownloadStatistics.Start(PR_IntervalNow());
|
||||
}
|
||||
|
||||
nsWaveStateMachine::~nsWaveStateMachine()
|
||||
|
@ -408,12 +443,11 @@ 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 (contentLength >= 0 && contentLength - mWavePCMOffset < length) {
|
||||
length = contentLength - mWavePCMOffset;
|
||||
if (mTotalBytes >= 0 && mTotalBytes - mWavePCMOffset < length) {
|
||||
length = mTotalBytes - mWavePCMOffset;
|
||||
}
|
||||
return BytesToTime(length);
|
||||
}
|
||||
|
@ -446,10 +480,23 @@ nsWaveStateMachine::IsEnded()
|
|||
}
|
||||
|
||||
void
|
||||
nsWaveStateMachine::StreamEnded()
|
||||
nsWaveStateMachine::StreamEnded(PRBool aAtEnd)
|
||||
{
|
||||
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
|
||||
|
@ -491,14 +538,13 @@ nsWaveStateMachine::Run()
|
|||
}
|
||||
break;
|
||||
|
||||
case STATE_BUFFERING:
|
||||
if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart) < mBufferingWait) &&
|
||||
mStream->DownloadRate() >= 0 &&
|
||||
case STATE_BUFFERING: {
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < mBufferingWait) &&
|
||||
mStream->Available() < mBufferingBytes) {
|
||||
LOG(PR_LOG_DEBUG, ("Buffering data until %d bytes or %d milliseconds (rate %f)\n",
|
||||
LOG(PR_LOG_DEBUG, ("Buffering data until %d bytes or %d milliseconds\n",
|
||||
mBufferingBytes - mStream->Available(),
|
||||
mBufferingWait - (PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart)),
|
||||
mStream->DownloadRate()));
|
||||
mBufferingWait - (now - mBufferingStart)));
|
||||
monitor.Wait(PR_MillisecondsToInterval(1000));
|
||||
} else {
|
||||
ChangeState(mNextState);
|
||||
|
@ -508,30 +554,26 @@ nsWaveStateMachine::Run()
|
|||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_PLAYING:
|
||||
case STATE_PLAYING: {
|
||||
if (!mAudioStream) {
|
||||
OpenAudioStream();
|
||||
} else {
|
||||
mAudioStream->Resume();
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// Assuming enough data is available from the network, we aim to
|
||||
// completely fill the audio backend's buffers with data. This
|
||||
|
@ -593,6 +635,7 @@ nsWaveStateMachine::Run()
|
|||
monitor.Wait(PR_MillisecondsToInterval(PRUint32(nextWakeup)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_SEEKING:
|
||||
{
|
||||
|
@ -759,6 +802,59 @@ 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)
|
||||
{
|
||||
|
@ -990,8 +1086,7 @@ nsWaveStateMachine::FindDataOffset()
|
|||
NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver)
|
||||
|
||||
nsWaveDecoder::nsWaveDecoder()
|
||||
: mBytesDownloaded(0),
|
||||
mInitialVolume(1.0),
|
||||
: mInitialVolume(1.0),
|
||||
mStream(nsnull),
|
||||
mTimeOffset(0.0),
|
||||
mEndedCurrentTime(0.0),
|
||||
|
@ -999,7 +1094,9 @@ nsWaveDecoder::nsWaveDecoder()
|
|||
mEnded(PR_FALSE),
|
||||
mNotifyOnShutdown(PR_FALSE),
|
||||
mSeekable(PR_TRUE),
|
||||
mResourceLoaded(PR_FALSE)
|
||||
mResourceLoaded(PR_FALSE),
|
||||
mMetadataLoadedReported(PR_FALSE),
|
||||
mResourceLoadedReported(PR_FALSE)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsWaveDecoder);
|
||||
}
|
||||
|
@ -1148,7 +1245,6 @@ nsWaveDecoder::Load(nsIURI* aURI, nsIChannel* aChannel, nsIStreamListener** aStr
|
|||
mStopping = PR_FALSE;
|
||||
|
||||
// Reset progress member variables
|
||||
mBytesDownloaded = 0;
|
||||
mResourceLoaded = PR_FALSE;
|
||||
|
||||
if (aStreamListener) {
|
||||
|
@ -1171,15 +1267,19 @@ 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);
|
||||
|
||||
|
@ -1198,15 +1298,13 @@ nsWaveDecoder::MetadataLoaded()
|
|||
mElement->FirstFrameLoaded();
|
||||
}
|
||||
|
||||
if (!mResourceLoaded) {
|
||||
mMetadataLoadedReported = PR_TRUE;
|
||||
|
||||
if (mResourceLoaded) {
|
||||
ResourceLoaded();
|
||||
} else {
|
||||
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
|
||||
|
@ -1228,28 +1326,25 @@ nsWaveDecoder::ResourceLoaded()
|
|||
if (mShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->StreamEnded(PR_TRUE);
|
||||
}
|
||||
|
||||
mResourceLoaded = PR_TRUE;
|
||||
|
||||
if (mElement) {
|
||||
mElement->ResourceLoaded();
|
||||
}
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->StreamEnded();
|
||||
}
|
||||
if (!mMetadataLoadedReported || mResourceLoadedReported)
|
||||
return;
|
||||
|
||||
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
|
||||
|
@ -1262,7 +1357,7 @@ nsWaveDecoder::NetworkError()
|
|||
mElement->NetworkError();
|
||||
}
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->StreamEnded();
|
||||
mPlaybackStateMachine->StreamEnded(PR_FALSE);
|
||||
}
|
||||
Stop();
|
||||
}
|
||||
|
@ -1285,28 +1380,63 @@ nsWaveDecoder::IsEnded() const
|
|||
return mEnded;
|
||||
}
|
||||
|
||||
PRUint64
|
||||
nsWaveDecoder::GetBytesLoaded()
|
||||
nsMediaDecoder::Statistics
|
||||
nsWaveDecoder::GetStatistics()
|
||||
{
|
||||
return mBytesDownloaded;
|
||||
if (!mPlaybackStateMachine)
|
||||
return Statistics();
|
||||
return mPlaybackStateMachine->GetStatistics();
|
||||
}
|
||||
|
||||
PRInt64
|
||||
nsWaveDecoder::GetTotalBytes()
|
||||
void
|
||||
nsWaveDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
|
||||
{
|
||||
return mContentLength;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::SetTotalBytes(PRInt64 aBytes)
|
||||
{
|
||||
mContentLength = aBytes;
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::UpdateBytesDownloaded(PRUint64 aBytes)
|
||||
{
|
||||
mBytesDownloaded = aBytes;
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->SetTotalBytes(aBytes);
|
||||
} else {
|
||||
NS_WARNING("Forgot total bytes since there is no state machine set up");
|
||||
}
|
||||
}
|
||||
|
||||
// An event that gets posted to the main thread, when the media element is
|
||||
|
@ -1354,27 +1484,20 @@ nsWaveDecoder::Observe(nsISupports* aSubject, const char* aTopic, const PRUnicha
|
|||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::BufferingStarted()
|
||||
nsWaveDecoder::UpdateReadyStateForData()
|
||||
{
|
||||
if (mShuttingDown) {
|
||||
if (!mElement || mShuttingDown || !mPlaybackStateMachine)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mElement) {
|
||||
mElement->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
|
||||
}
|
||||
PRBool haveDataToPlay =
|
||||
mPlaybackStateMachine->HasPendingData() && mMetadataLoadedReported;
|
||||
mElement->UpdateReadyStateForData(haveDataToPlay);
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::BufferingStopped()
|
||||
{
|
||||
if (mShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mElement) {
|
||||
mElement->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
|
||||
}
|
||||
UpdateReadyStateForData();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1402,6 +1525,7 @@ nsWaveDecoder::SeekingStopped()
|
|||
|
||||
if (mElement) {
|
||||
mElement->SeekCompleted();
|
||||
UpdateReadyStateForData();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ _TEST_FILES += \
|
|||
test_ended1.html \
|
||||
test_ended2.html \
|
||||
test_onloadedmetadata.html \
|
||||
test_progress1.html \
|
||||
test_progress3.html \
|
||||
test_standalone.html \
|
||||
test_timeupdate1.html \
|
||||
|
@ -96,7 +97,6 @@ _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 \
|
||||
|
|
|
@ -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,7 +105,9 @@ 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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,8 @@ function late_add_sources_first(element, name, type) {
|
|||
}
|
||||
|
||||
function check_ogg(e) {
|
||||
ok(e.videoWidth == 320 && e.videoHeight == 240, "video should be 320x240");
|
||||
is(e.videoWidth, 320, "video width " + e.currentSrc);
|
||||
is(e.videoHeight, 240, "video height " + e.currentSrc);
|
||||
}
|
||||
|
||||
function check_wav(e) {
|
||||
|
|
|
@ -12,20 +12,23 @@
|
|||
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: " + v);
|
||||
ok(v, "Found video element after metadata loaded");
|
||||
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: " + completed);
|
||||
ok(!completed, "Check for progress event after completed");
|
||||
ok(e.lengthComputable, "Check progress lengthComputable");
|
||||
ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
|
||||
ok(e.total == 285310, "Check progress total: " + e.total);
|
||||
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");
|
||||
last_progress = e.loaded;
|
||||
}
|
||||
|
||||
|
@ -33,8 +36,8 @@ function do_ended() {
|
|||
dump('test_progress1: do_ended\n');
|
||||
ok(!completed, "Check for duplicate ended event");
|
||||
completed = true;
|
||||
ok(last_progress == 285310, "Last progress event size: " + last_progress);
|
||||
ok(load_count == 1, "load event raised: " + load_count);
|
||||
is(last_progress, 285310, "Last progress event size");
|
||||
is(load_count, 1, "load event raised");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// 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');
|
||||
|
@ -19,17 +20,19 @@ function on_loadedmetadata() {
|
|||
}
|
||||
|
||||
function do_progress(e) {
|
||||
ok(!completed, "Check for progress event after completed: " + completed);
|
||||
ok(!completed, "Check for progress event after completed");
|
||||
ok(e.lengthComputable, "Check progress lengthComputable");
|
||||
ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
|
||||
ok(e.total == 102444, "Check progress total: " + e.total);
|
||||
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");
|
||||
last_progress = e.loaded;
|
||||
}
|
||||
|
||||
function do_ended() {
|
||||
ok(!completed, "Check for duplicate ended event");
|
||||
completed = true;
|
||||
ok(last_progress == 102444, "Last progress event size: " + last_progress);
|
||||
is(last_progress, 102444, "Last progress event size");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,44 +14,47 @@
|
|||
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: " + v);
|
||||
ok(v, "Found video element after metadata loaded");
|
||||
v.play();
|
||||
dump('test_progress1: on_loadedmetadata exiting\n');
|
||||
dump('test_progress3: on_loadedmetadata exiting\n');
|
||||
}
|
||||
|
||||
function do_progress(e) {
|
||||
dump('test_progress1: do_progress ' + e.loaded + '\n');
|
||||
ok(!completed, "Check for progress event after completed: " + completed);
|
||||
dump('test_progress3: do_progress ' + e.loaded + '/' + e.total + '\n');
|
||||
ok(!completed, "Check for progress event after completed");
|
||||
ok(e.lengthComputable, "Check progress lengthComputable");
|
||||
ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
|
||||
ok(e.total == 28942, "Check progress total: " + e.total);
|
||||
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");
|
||||
last_progress = e.loaded;
|
||||
}
|
||||
|
||||
function do_ended() {
|
||||
dump('test_progress1: do_ended\n');
|
||||
dump('test_progress3: do_ended\n');
|
||||
ok(!completed, "Check for duplicate ended event");
|
||||
completed = true;
|
||||
ok(last_progress == 28942, "Last progress event size: " + last_progress);
|
||||
ok(load_count == 1, "load event raised: " + load_count);
|
||||
is(last_progress, 28942, "Last progress event size");
|
||||
is(load_count, 1, "load event raised");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function do_load(e) {
|
||||
load_count++;
|
||||
dump('test_progress1: do_loaded ' + e.loaded + "\n");
|
||||
dump('test_progress3: do_loaded ' + e.loaded + "\n");
|
||||
}
|
||||
|
||||
function do_timeupdate() {
|
||||
var v = document.getElementById('v');
|
||||
dump('test_progress1: timeupdate: ' + v.currentTime + "\n");
|
||||
dump('test_progress3: timeupdate: ' + v.currentTime + "\n");
|
||||
}
|
||||
|
||||
function do_play() {
|
||||
dump('test_progress1: do_play\n');
|
||||
dump('test_progress3: do_play\n');
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
// 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');
|
||||
|
@ -21,17 +22,19 @@ function on_loadedmetadata() {
|
|||
}
|
||||
|
||||
function do_progress(e) {
|
||||
ok(!completed, "Check for progress event after completed: " + completed);
|
||||
ok(!completed, "Check for progress event after completed");
|
||||
ok(e.lengthComputable, "Check progress lengthComputable");
|
||||
ok(e.loaded >= 0 && e.loaded <= e.total, "Check progress loaded: " + e.loaded);
|
||||
ok(e.total == 11069, "Check progress total: " + e.total);
|
||||
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");
|
||||
last_progress = e.loaded;
|
||||
}
|
||||
|
||||
function do_ended() {
|
||||
ok(!completed, "Check for duplicate ended event");
|
||||
completed = true;
|
||||
ok(last_progress == 11069, "Last progress event size: " + last_progress);
|
||||
is(last_progress, 11069, "Last progress event size");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче