From 803c5031844265b18bf7035f51368d7581485f0e Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Wed, 19 Jun 2013 16:05:52 +1200 Subject: [PATCH] Bug 883731. Part 3: Make all calls to ChangeReadyState go through UpdateReadyStateForDtaData. r=cpearce --HG-- extra : rebase_source : 49e39cedd64bcc52bcda452fb38c2d8f460672a0 --- .../html/content/public/HTMLMediaElement.h | 26 ++++-- content/html/content/src/HTMLMediaElement.cpp | 92 ++++++++----------- content/media/MediaDecoder.cpp | 2 + 3 files changed, 58 insertions(+), 62 deletions(-) diff --git a/content/html/content/public/HTMLMediaElement.h b/content/html/content/public/HTMLMediaElement.h index 6c426eca110..199117def50 100644 --- a/content/html/content/public/HTMLMediaElement.h +++ b/content/html/content/public/HTMLMediaElement.h @@ -229,17 +229,11 @@ public: // Dispatch events that were raised while in the bfcache nsresult DispatchPendingMediaEvents(); - // 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. + // Called every time readyState might need to be updated. + // aNextFrame indicates whether the next frame is available. This method will + // choose the correct value for readyState. virtual void UpdateReadyStateForData(NextFrameStatus aNextFrame) MOZ_FINAL MOZ_OVERRIDE; - // Use this method to change the mReadyState member, so required - // events can be fired. - void ChangeReadyState(nsMediaReadyState aState); - // Return true if we can activate autoplay assuming enough data has arrived. bool CanActivateAutoplay(); @@ -632,6 +626,12 @@ protected: nsIStreamListener **aListener, MediaDecoder* aCloneDonor); + /** + * Use this method to change the mReadyState member, so required + * events can be fired. Only UpdateReadyStateForData should call this. + */ + void ChangeReadyState(nsMediaReadyState aState); + /** * Call this after setting up mLoadingSrc and mDecoder. */ @@ -1016,9 +1016,15 @@ protected: // Set to false when completed, or not yet started. bool mBegun; + // True when the decoder has called MetadataLoaded + bool mMetadataLoaded; + // True when the decoder has loaded enough data to display the // first frame of the content. - bool mLoadedFirstFrame; + bool mFirstFrameLoaded; + + // True when loadeddata has been fired for this resource. + bool mFiredLoadedData; // Indicates whether current playback is a result of user action // (ie. calling of the Play method), or automatic playback due to diff --git a/content/html/content/src/HTMLMediaElement.cpp b/content/html/content/src/HTMLMediaElement.cpp index ff57b2e5092..c1c7fca65f3 100644 --- a/content/html/content/src/HTMLMediaElement.cpp +++ b/content/html/content/src/HTMLMediaElement.cpp @@ -606,7 +606,10 @@ void HTMLMediaElement::AbortExistingLoads() } mError = nullptr; - mLoadedFirstFrame = false; + mMetadataLoaded = false; + mFirstFrameLoaded = false; + mFiredLoadedData = false; + mBegun = false; mAutoplaying = true; mIsLoadingFromSourceChildren = false; mSuspendedAfterFirstFrame = false; @@ -626,7 +629,7 @@ void HTMLMediaElement::AbortExistingLoads() if (mNetworkState != NETWORK_EMPTY) { mNetworkState = NETWORK_EMPTY; NS_ASSERTION(!mDecoder && !mSrcStream, "How did someone setup a new stream/decoder already?"); - ChangeReadyState(HAVE_NOTHING); + UpdateReadyStateForData(NEXT_FRAME_UNAVAILABLE); mPaused = true; if (fireTimeUpdate) { @@ -778,7 +781,7 @@ void HTMLMediaElement::SelectResource() // AddRemoveSelfReference, since it must still be held DispatchAsyncEvent(NS_LITERAL_STRING("loadstart")); - // Delay setting mIsRunningSeletResource until after UpdatePreloadAction + // Delay setting mIsRunningSelectResource until after UpdatePreloadAction // so that we don't lose our state change by bailing out of the preload // state update UpdatePreloadAction(); @@ -1887,7 +1890,9 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed aNodeInfo) mCurrentPlayRangeStart(-1.0), mAllowAudioData(false), mBegun(false), - mLoadedFirstFrame(false), + mMetadataLoaded(false), + mFirstFrameLoaded(false), + mFiredLoadedData(false), mAutoplaying(true), mAutoplayEnabled(true), mPaused(true), @@ -2722,53 +2727,38 @@ void HTMLMediaElement::MetadataLoaded(int aChannels, bool aHasVideo, const MetadataTags* aTags) { + mMetadataLoaded = true; mChannels = aChannels; mRate = aRate; mHasAudio = aHasAudio; mHasVideo = aHasVideo; mTags = aTags; - ChangeReadyState(HAVE_METADATA); + + UpdateReadyStateForData(NEXT_FRAME_UNAVAILABLE); + DispatchAsyncEvent(NS_LITERAL_STRING("durationchange")); DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata")); + if (mDecoder && mDecoder->IsTransportSeekable() && mDecoder->IsMediaSeekable()) { ProcessMediaFragmentURI(); mDecoder->SetFragmentEndTime(mFragmentEnd); } - - // If this element had a video track, but consists only of an audio track now, - // delete the VideoFrameContainer. This happens when the src is changed to an - // audio only file. - if (!aHasVideo) { - mVideoFrameContainer = nullptr; - } } void HTMLMediaElement::FirstFrameLoaded() { + mFirstFrameLoaded = true; ChangeDelayLoadStatus(false); + + // The current frame is available, but the *next* frame is not. UpdateReadyStateForData(NEXT_FRAME_UNAVAILABLE); NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended"); - if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused && !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) && mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) { mSuspendedAfterFirstFrame = true; mDecoder->Suspend(); - } else if (mLoadedFirstFrame && - mDownloadSuspendedByCache && - mDecoder && - !mDecoder->IsEnded()) { - // We've already loaded the first frame, and the decoder has signalled - // that the download has been suspended by the media cache. So move - // readyState into HAVE_ENOUGH_DATA, in case there's script waiting - // for a "canplaythrough" event; without this forced transition, we will - // never fire the "canplaythrough" event if the media cache is so small - // that the download was suspended before the first frame was loaded. - // Don't force this transition if the decoder is in ended state; the - // readyState should remain at HAVE_CURRENT_DATA in this case. - ChangeReadyState(HAVE_ENOUGH_DATA); - return; } } @@ -2859,7 +2849,7 @@ void HTMLMediaElement::PlaybackEnded() void HTMLMediaElement::SeekStarted() { DispatchAsyncEvent(NS_LITERAL_STRING("seeking")); - ChangeReadyState(HAVE_METADATA); + UpdateReadyStateForData(NEXT_FRAME_UNAVAILABLE); FireTimeUpdate(false); } @@ -2914,18 +2904,26 @@ void HTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame) { mLastNextFrameStatus = aNextFrame; - if (mReadyState < HAVE_METADATA) { - // aNextFrame might have a next frame because the decoder can advance - // on its own thread before MetadataLoaded gets - // a chance to run. - // The arrival of more data can't change us out of this readyState. + if (!mMetadataLoaded) { + ChangeReadyState(HAVE_NOTHING); return; } - if (mReadyState > HAVE_METADATA && - mDownloadSuspendedByCache && - mDecoder && - !mDecoder->IsEnded()) { + if (!mFirstFrameLoaded || Seeking()) { + ChangeReadyState(HAVE_METADATA); + return; + } + + if (mHasVideo) { + VideoFrameContainer* container = GetVideoFrameContainer(); + if (container && mMediaSize == nsIntSize(-1,-1)) { + // No frame has been set yet. Don't advance out of HAVE_METADATA. + ChangeReadyState(HAVE_METADATA); + return; + } + } + + if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) { // The decoder has signalled that the download has been suspended by the // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's // script waiting for a "canplaythrough" event; without this forced @@ -2939,14 +2937,6 @@ void HTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame) return; } - if (mReadyState < HAVE_CURRENT_DATA && mHasVideo) { - VideoFrameContainer* container = GetVideoFrameContainer(); - if (container && mMediaSize == nsIntSize(-1,-1)) { - // No frame has been set yet. Don't advance. - return; - } - } - if (aNextFrame != NEXT_FRAME_AVAILABLE) { ChangeReadyState(HAVE_CURRENT_DATA); if (!mWaitingFired && aNextFrame == NEXT_FRAME_UNAVAILABLE_BUFFERING) { @@ -2973,11 +2963,11 @@ void HTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame) MediaDecoder::Statistics stats = mDecoder->GetStatistics(); if (stats.mTotalBytes < 0 ? stats.mDownloadRateReliable : stats.mTotalBytes == stats.mDownloadPosition || - mDecoder->CanPlayThrough()) - { + mDecoder->CanPlayThrough()) { ChangeReadyState(HAVE_ENOUGH_DATA); return; } + ChangeReadyState(HAVE_FUTURE_DATA); } @@ -3011,12 +3001,10 @@ void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState) DispatchAsyncEvent(NS_LITERAL_STRING("waiting")); } - if (oldState < HAVE_CURRENT_DATA && - mReadyState >= HAVE_CURRENT_DATA && - !mLoadedFirstFrame) - { + if (oldState < HAVE_CURRENT_DATA && mReadyState >= HAVE_CURRENT_DATA && + !mFiredLoadedData) { DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata")); - mLoadedFirstFrame = true; + mFiredLoadedData = true; } if (mReadyState == HAVE_CURRENT_DATA) { diff --git a/content/media/MediaDecoder.cpp b/content/media/MediaDecoder.cpp index 632d2722370..61824d92436 100644 --- a/content/media/MediaDecoder.cpp +++ b/content/media/MediaDecoder.cpp @@ -740,6 +740,8 @@ void MediaDecoder::MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool // our new size. Invalidate(); mOwner->MetadataLoaded(aChannels, aRate, aHasAudio, aHasVideo, aTags); + // Dispatch an initial progress event to represent the data read so far + mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("progress")); } StartProgress();