Bug 879717: Part 1-Delay entering HAVE_CURRENT_DATA state until a video frame has been stored in the image container. r=cpearce
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -1008,16 +1008,19 @@ protected:
// These events get re-dispatched when the bfcache is exited.
nsTArray<nsString> mPendingEvents;
// Media loading flags. See:
// http://www.whatwg.org/specs/web-apps/current-work/#video)
nsMediaNetworkState mNetworkState;
nsMediaReadyState mReadyState;
+ // Last value passed from codec or stream source to UpdateReadyStateForData.
+ NextFrameStatus mLastNextFrameStatus;
+
enum LoadAlgorithmState {
// No load algorithm instance is waiting for a source to be added to the
// media in order to continue loading.
NOT_WAITING,
// We've run the load algorithm, and we tried all source children of the
// media element, and failed to load any successfully. We're waiting for
// another source element to be added to the media element, and will try
// to load any such element when its added.
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -66,16 +66,18 @@
#include "nsURIHashKey.h"
#include "nsJSUtils.h"
#include "MediaStreamGraph.h"
#include "nsIScriptError.h"
#include "nsHostObjectProtocolHandler.h"
#include "mozilla/dom/MediaSource.h"
#include "MediaMetadataManager.h"
#include "MediaSourceDecoder.h"
+#include "AudioStreamTrack.h"
+#include "VideoStreamTrack.h"
#include "AudioChannelService.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/AudioTrackList.h"
@@ -659,17 +661,20 @@ void HTMLMediaElement::AbortExistingLoad
mLoadedDataFired = false;
mAutoplaying = true;
mIsLoadingFromSourceChildren = false;
mSuspendedAfterFirstFrame = false;
mAllowSuspendAfterFirstFrame = true;
mHaveQueuedSelectResource = false;
mSuspendedForPreloadNone = false;
mDownloadSuspendedByCache = false;
+ mHasAudio = false;
+ mHasVideo = false;
mSourcePointer = nullptr;
+ mLastNextFrameStatus = NEXT_FRAME_UNINITIALIZED;
mTags = nullptr;
if (mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
NS_ASSERTION(!mDecoder && !mSrcStream, "How did someone setup a new stream/decoder already?");
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
mPaused = true;
@@ -1982,16 +1987,17 @@ HTMLMediaElement::LookupMediaElementURIT
}
HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: nsGenericHTMLElement(aNodeInfo),
mSrcStreamListener(nullptr),
mCurrentLoadID(0),
mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING),
+ mLastNextFrameStatus(NEXT_FRAME_UNINITIALIZED),
mLoadWaitStatus(NOT_WAITING),
mVolume(1.0),
mPreloadAction(PRELOAD_UNDEFINED),
mMediaSize(-1,-1),
mLastCurrentTime(0.0),
mFragmentStart(-1.0),
mFragmentEnd(-1.0),
mDefaultPlaybackRate(1.0),
@@ -2823,32 +2829,43 @@ void HTMLMediaElement::SetupSrcMediaStre
mSrcStreamListener = new StreamListener(this);
GetSrcMediaStream()->AddListener(mSrcStreamListener);
if (mPaused) {
GetSrcMediaStream()->ChangeExplicitBlockerCount(1);
}
if (mPausedForInactiveDocumentOrChannel) {
GetSrcMediaStream()->ChangeExplicitBlockerCount(1);
}
+
+ nsAutoTArray<nsRefPtr<AudioStreamTrack>,1> audioTracks;
+ aStream->GetAudioTracks(audioTracks);
+ nsAutoTArray<nsRefPtr<VideoStreamTrack>,1> videoTracks;
+ aStream->GetVideoTracks(videoTracks);
+
+ // Clear aTags, but set mHasAudio and mHasVideo
+ MediaInfo mediaInfo;
+ mediaInfo.mAudio.mHasAudio = !audioTracks.IsEmpty();
+ mediaInfo.mVideo.mHasVideo = !videoTracks.IsEmpty();
+ MetadataLoaded(&mediaInfo, nullptr);
+
+ DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
+ mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
+
ChangeDelayLoadStatus(false);
GetSrcMediaStream()->AddAudioOutput(this);
GetSrcMediaStream()->SetAudioOutputVolume(this, float(mMuted ? 0.0 : mVolume));
VideoFrameContainer* container = GetVideoFrameContainer();
if (container) {
GetSrcMediaStream()->AddVideoOutput(container);
}
// Note: we must call DisconnectTrackListListeners(...) before dropping
// mSrcStream
mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
- ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
- DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
- DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
- ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
AddRemoveSelfReference();
// FirstFrameLoaded() will be called when the stream has current data.
}
void HTMLMediaElement::EndSrcMediaStreamPlayback()
{
MediaStream* stream = GetSrcMediaStream();
if (stream) {
@@ -2918,16 +2935,17 @@ void HTMLMediaElement::MetadataLoaded(co
}
}
void HTMLMediaElement::FirstFrameLoaded()
{
NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
ChangeDelayLoadStatus(false);
+ UpdateReadyStateForData(NEXT_FRAME_UNAVAILABLE);
if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
!HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
mSuspendedAfterFirstFrame = true;
mDecoder->Suspend();
}
}
@@ -3080,16 +3098,18 @@ void HTMLMediaElement::DownloadStalled()
bool HTMLMediaElement::ShouldCheckAllowOrigin()
{
return mCORSMode != CORS_NONE;
}
void HTMLMediaElement::UpdateReadyStateForData(MediaDecoderOwner::NextFrameStatus aNextFrame)
{
+ mLastNextFrameStatus = aNextFrame;
+
if (mReadyState < nsIDOMHTMLMediaElement::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.
return;
}
if (aNextFrame == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
@@ -3106,16 +3126,24 @@ void HTMLMediaElement::UpdateReadyStateF
// this transition if the decoder is in ended state; the readyState
// should remain at HAVE_CURRENT_DATA in this case.
// Note that this state transition includes the case where we finished
// downloaded the whole data stream.
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
return;
}
+ if (mReadyState < nsIDOMHTMLMediaElement::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 != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) {
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
if (!mWaitingFired && aNextFrame == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING) {
FireTimeUpdate(false);
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
mWaitingFired = true;
}
return;
@@ -3282,34 +3310,24 @@ void HTMLMediaElement::CheckAutoplayData
GetSrcMediaStream()->ChangeExplicitBlockerCount(-1);
}
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
}
}
VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer()
{
- // If we have loaded the metadata, and the size of the video is still
- // (-1, -1), the media has no video. Don't go a create a video frame
- // container.
- if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
- mMediaSize == nsIntSize(-1, -1)) {
- return nullptr;
- }
+ if (mVideoFrameContainer)
+ return mVideoFrameContainer;
// Only video frames need an image container.
if (!IsVideo()) {
return nullptr;
}
- mHasVideo = true;
-
- if (mVideoFrameContainer)
- return mVideoFrameContainer;
-
mVideoFrameContainer =
new VideoFrameContainer(this, LayerManager::CreateAsynchronousImageContainer());
return mVideoFrameContainer;
}
nsresult HTMLMediaElement::DispatchEvent(const nsAString& aName)
{
@@ -3407,16 +3425,17 @@ void HTMLMediaElement::NotifyDecoderPrin
OutputMediaStream* ms = &mOutputStreams[i];
ms->mStream->CombineWithPrincipal(principal);
}
}
void HTMLMediaElement::UpdateMediaSize(nsIntSize size)
{
mMediaSize = size;
+ UpdateReadyStateForData(mLastNextFrameStatus);
}
void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents)
{
if (aPauseElement != mPausedForInactiveDocumentOrChannel) {
mPausedForInactiveDocumentOrChannel = aPauseElement;
if (aPauseElement) {
#ifdef MOZ_EME