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.