Bug 543769 - Buffer non-autobuffer videos upon first playback to ensure smooth playback. r=roc a=blocking2.0

This commit is contained in:
Chris Pearce 2010-07-23 10:48:32 +12:00
parent e3f81960a0
commit 6aa46b1e86
9 changed files with 89 additions and 58 deletions

View File

@ -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();
}

View File

@ -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()

View File

@ -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();

View File

@ -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"));
}

View File

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

View File

@ -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;
}

View File

@ -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.

View File

@ -1639,7 +1639,7 @@ nsWaveDecoder::Suspend()
}
void
nsWaveDecoder::Resume()
nsWaveDecoder::Resume(PRBool aForceBuffering)
{
if (mStream) {
mStream->Resume();

View File

@ -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.