diff --git a/content/html/content/src/nsHTMLMediaElement.cpp b/content/html/content/src/nsHTMLMediaElement.cpp
index 6dbe2602a18..3a3f6b97b3b 100644
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1010,7 +1010,7 @@ void nsHTMLMediaElement::StopSuspendingAfterFirstFrame()
return;
mSuspendedAfterFirstFrame = PR_FALSE;
if (mDecoder) {
- mDecoder->Resume();
+ mDecoder->Resume(PR_TRUE);
}
}
@@ -1662,11 +1662,6 @@ 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(NextFrameStatus aNextFrame)
{
if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
@@ -1677,8 +1672,6 @@ void nsHTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame)
return;
}
- nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
-
if (aNextFrame != NEXT_FRAME_AVAILABLE) {
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
if (!mWaitingFired && aNextFrame == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
@@ -1693,25 +1686,17 @@ void nsHTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame)
// make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
// we've downloaded enough data that our download rate is considered
// reliable. We have to move to HAVE_ENOUGH_DATA at some point or
- // autoplay elements for live streams will never play.
+ // autoplay elements for live streams will never play. Otherwise we
+ // move to HAVE_ENOUGH_DATA if we can play through the entire media
+ // without stopping to buffer.
+ nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
if (stats.mTotalBytes < 0 ? stats.mDownloadRateReliable :
- stats.mTotalBytes == stats.mDownloadPosition) {
+ stats.mTotalBytes == stats.mDownloadPosition ||
+ mDecoder->CanPlayThrough())
+ {
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;
- if (timeToDownload <= timeToPlay) {
- ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
- return;
- }
- }
-
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
}
@@ -1951,7 +1936,7 @@ void nsHTMLMediaElement::NotifyOwnerDocumentActivityChanged()
mDecoder->Pause();
mDecoder->Suspend();
} else {
- mDecoder->Resume();
+ mDecoder->Resume(PR_FALSE);
if (!mPaused && !mDecoder->IsEnded()) {
mDecoder->Play();
}
diff --git a/content/media/nsBuiltinDecoder.cpp b/content/media/nsBuiltinDecoder.cpp
index 4ecc247c588..ee7c568c8a1 100644
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -446,7 +446,8 @@ NS_IMETHODIMP nsBuiltinDecoder::Observe(nsISupports *aSubjet,
nsMediaDecoder::Statistics
nsBuiltinDecoder::GetStatistics()
{
- NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+ NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
+ "Should be on main or state machine thread.");
Statistics result;
MonitorAutoEnter mon(mMonitor);
@@ -788,12 +789,16 @@ void nsBuiltinDecoder::Suspend()
}
}
-void nsBuiltinDecoder::Resume()
+void nsBuiltinDecoder::Resume(PRBool aForceBuffering)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mStream) {
mStream->Resume();
}
+ if (aForceBuffering) {
+ MonitorAutoEnter mon(mMonitor);
+ mDecoderStateMachine->StartBuffering();
+ }
}
void nsBuiltinDecoder::StopProgressUpdates()
diff --git a/content/media/nsBuiltinDecoder.h b/content/media/nsBuiltinDecoder.h
index 957bde32fc1..21deb67608b 100644
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -293,6 +293,12 @@ public:
// Only called on the decoder thread. Must be called with
// the decode monitor held.
virtual void UpdatePlaybackPosition(PRInt64 aTime) = 0;
+
+ // Causes the state machine to switch to buffering state, and to
+ // immediately stop playback and buffer downloaded data. Must be called
+ // with the decode monitor held. Called on the state machine thread and
+ // the main thread.
+ virtual void StartBuffering() = 0;
};
class nsBuiltinDecoder : public nsMediaDecoder
@@ -393,7 +399,7 @@ class nsBuiltinDecoder : public nsMediaDecoder
// Resume any media downloads that have been suspended. Called by the
// media element when it is restored from the bfcache. Call on the
// main thread only.
- virtual void Resume();
+ virtual void Resume(PRBool aForceBuffering);
// Tells our nsMediaStream to put all loads in the background.
virtual void MoveLoadsToBackground();
diff --git a/content/media/nsBuiltinDecoderStateMachine.cpp b/content/media/nsBuiltinDecoderStateMachine.cpp
index f1395856679..33c2b284d07 100644
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -817,29 +817,7 @@ nsresult nsBuiltinDecoderStateMachine::Run()
// 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 (IsPlaying()) {
- StopPlayback(AUDIO_PAUSE);
- mDecoder->GetMonitor().NotifyAll();
- }
-
- // We need to tell the element that buffering has started.
- // We can't just directly send an asynchronous runnable that
- // eventually fires the "waiting" event. The problem is that
- // there might be pending main-thread events, such as "data
- // received" notifications, that mean we're not actually still
- // buffering by the time this runnable executes. So instead
- // we just trigger UpdateReadyStateForData; when it runs, it
- // will check the current state and decide whether to tell
- // the element we're buffering or not.
- UpdateReadyState();
-
- mBufferingStart = TimeStamp::Now();
- PRPackedBool reliable;
- double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
- mBufferingEndOffset = mDecoder->mDecoderPosition +
- BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
- mState = DECODER_STATE_BUFFERING;
- LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
+ StartBuffering();
} else {
if (mBufferExhausted) {
// This will wake up the decode thread and force it to try to
@@ -943,10 +921,12 @@ nsresult nsBuiltinDecoderStateMachine::Run()
case DECODER_STATE_BUFFERING:
{
TimeStamp now = TimeStamp::Now();
- if (now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
- mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
- !mDecoder->GetCurrentStream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
- !mDecoder->GetCurrentStream()->IsSuspendedByCache()) {
+ nsMediaStream* stream = mDecoder->GetCurrentStream();
+ if (!mDecoder->CanPlayThrough() &&
+ now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
+ stream->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
+ !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+ !stream->IsSuspendedByCache()) {
LOG(PR_LOG_DEBUG,
("In buffering: buffering data until %d bytes available or %f seconds",
PRUint32(mBufferingEndOffset - mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
@@ -1273,3 +1253,32 @@ void nsBuiltinDecoderStateMachine::LoadMetadata()
return;
}
}
+
+void nsBuiltinDecoderStateMachine::StartBuffering()
+{
+ mDecoder->GetMonitor().AssertCurrentThreadIn();
+ mBufferExhausted = PR_TRUE;
+ if (IsPlaying()) {
+ StopPlayback(AUDIO_PAUSE);
+ mDecoder->GetMonitor().NotifyAll();
+ }
+
+ // We need to tell the element that buffering has started.
+ // We can't just directly send an asynchronous runnable that
+ // eventually fires the "waiting" event. The problem is that
+ // there might be pending main-thread events, such as "data
+ // received" notifications, that mean we're not actually still
+ // buffering by the time this runnable executes. So instead
+ // we just trigger UpdateReadyStateForData; when it runs, it
+ // will check the current state and decide whether to tell
+ // the element we're buffering or not.
+ UpdateReadyState();
+
+ mBufferingStart = TimeStamp::Now();
+ PRPackedBool reliable;
+ double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
+ mBufferingEndOffset = mDecoder->mDecoderPosition +
+ BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
+ mState = DECODER_STATE_BUFFERING;
+ LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
+}
diff --git a/content/media/nsBuiltinDecoderStateMachine.h b/content/media/nsBuiltinDecoderStateMachine.h
index 91860232b61..1c545fcd7d6 100644
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -168,6 +168,7 @@ public:
virtual void ClearPositionChangeFlag();
virtual void SetSeekable(PRBool aSeekable);
virtual void UpdatePlaybackPosition(PRInt64 aTime);
+ virtual void StartBuffering();
// Load metadata Called on the state machine thread. The decoder monitor must be held with
diff --git a/content/media/nsMediaDecoder.cpp b/content/media/nsMediaDecoder.cpp
index caa9bade168..17b42c08e98 100644
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -231,3 +231,22 @@ void nsMediaDecoder::SetVideoData(const gfxIntSize& aSize,
mImageContainer->SetCurrentImage(aImage);
}
}
+
+// 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;
+
+PRBool nsMediaDecoder::CanPlayThrough()
+{
+ Statistics stats = GetStatistics();
+ if (!stats.mDownloadRateReliable || !stats.mPlaybackRateReliable) {
+ return PR_FALSE;
+ }
+ PRInt64 bytesToDownload = stats.mTotalBytes - stats.mDownloadPosition;
+ PRInt64 bytesToPlayback = stats.mTotalBytes - stats.mPlaybackPosition;
+ double timeToDownload =
+ (bytesToDownload + gDownloadSizeSafetyMargin)/stats.mDownloadRate;
+ double timeToPlay = bytesToPlayback/stats.mPlaybackRate;
+ return timeToDownload <= timeToPlay;
+}
diff --git a/content/media/nsMediaDecoder.h b/content/media/nsMediaDecoder.h
index 2c5a4fc3d48..6b0966da705 100644
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -209,8 +209,10 @@ public:
// media element when it is restored from the bfcache, or when we need
// to stop throttling the download. Call on the main thread only.
// The download will only actually resume once as many Resume calls
- // have been made as Suspend calls.
- virtual void Resume() = 0;
+ // have been made as Suspend calls. When aForceBuffering is PR_TRUE,
+ // we force the decoder to go into buffering state before resuming
+ // playback.
+ virtual void Resume(PRBool aForceBuffering) = 0;
// Returns a weak reference to the media element we're decoding for,
// if it's available.
@@ -234,6 +236,10 @@ public:
float aPixelAspectRatio,
Image* aImage);
+ // Returns PR_TRUE if we can play the entire media through without stopping
+ // to buffer, given the current download and playback rates.
+ PRBool CanPlayThrough();
+
protected:
// Start timer to update download progress information.
diff --git a/content/media/wave/nsWaveDecoder.cpp b/content/media/wave/nsWaveDecoder.cpp
index 967de5b8326..b814083189d 100644
--- a/content/media/wave/nsWaveDecoder.cpp
+++ b/content/media/wave/nsWaveDecoder.cpp
@@ -1639,7 +1639,7 @@ nsWaveDecoder::Suspend()
}
void
-nsWaveDecoder::Resume()
+nsWaveDecoder::Resume(PRBool aForceBuffering)
{
if (mStream) {
mStream->Resume();
diff --git a/content/media/wave/nsWaveDecoder.h b/content/media/wave/nsWaveDecoder.h
index 95874597515..ebb34bf7ee8 100644
--- a/content/media/wave/nsWaveDecoder.h
+++ b/content/media/wave/nsWaveDecoder.h
@@ -219,7 +219,7 @@ class nsWaveDecoder : public nsMediaDecoder
// Resume any media downloads that have been suspended. Called by the
// media element when it is restored from the bfcache. Call on the
// main thread only.
- virtual void Resume();
+ virtual void Resume(PRBool aForceBuffering);
// Calls mElement->UpdateReadyStateForData, telling it which state we have
// entered. Main thread only.