Bug 879717 - Part 1 - Delay entering HAVE_CURRENT_DATA state until a video frame has been stored in the image container. r=roc

This commit is contained in:
Andreas Pehrson 2014-11-26 18:29:00 +01:00
Родитель c1b3e5deb9
Коммит d92e123dc4
3 изменённых файлов: 109 добавлений и 20 удалений

Просмотреть файл

@ -71,6 +71,8 @@
#include "mozilla/dom/MediaSource.h" #include "mozilla/dom/MediaSource.h"
#include "MediaMetadataManager.h" #include "MediaMetadataManager.h"
#include "MediaSourceDecoder.h" #include "MediaSourceDecoder.h"
#include "AudioStreamTrack.h"
#include "VideoStreamTrack.h"
#include "AudioChannelService.h" #include "AudioChannelService.h"
@ -663,7 +665,10 @@ void HTMLMediaElement::AbortExistingLoads()
mHaveQueuedSelectResource = false; mHaveQueuedSelectResource = false;
mSuspendedForPreloadNone = false; mSuspendedForPreloadNone = false;
mDownloadSuspendedByCache = false; mDownloadSuspendedByCache = false;
mHasAudio = false;
mHasVideo = false;
mSourcePointer = nullptr; mSourcePointer = nullptr;
mLastNextFrameStatus = NEXT_FRAME_UNINITIALIZED;
mTags = nullptr; mTags = nullptr;
@ -897,6 +902,30 @@ void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack)
} }
} }
void HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream)
{
if (!mSrcStream || mSrcStream != aStream) {
return;
}
bool oldHasVideo = mHasVideo;
nsAutoTArray<nsRefPtr<AudioStreamTrack>,1> audioTracks;
mSrcStream->GetAudioTracks(audioTracks);
nsAutoTArray<nsRefPtr<VideoStreamTrack>,1> videoTracks;
mSrcStream->GetVideoTracks(videoTracks);
mHasAudio = !audioTracks.IsEmpty();
mHasVideo = !videoTracks.IsEmpty();
if (IsVideo() && oldHasVideo != mHasVideo) {
// We are a video element and mHasVideo changed so update the screen wakelock
NotifyOwnerDocumentActivityChanged();
}
UpdateReadyStateForData(mLastNextFrameStatus);
}
void HTMLMediaElement::LoadFromSourceChildren() void HTMLMediaElement::LoadFromSourceChildren()
{ {
NS_ASSERTION(mDelayingLoadEvent, NS_ASSERTION(mDelayingLoadEvent,
@ -1981,6 +2010,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
mCurrentLoadID(0), mCurrentLoadID(0),
mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY), mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING), mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING),
mLastNextFrameStatus(NEXT_FRAME_UNINITIALIZED),
mLoadWaitStatus(NOT_WAITING), mLoadWaitStatus(NOT_WAITING),
mVolume(1.0), mVolume(1.0),
mPreloadAction(PRELOAD_UNDEFINED), mPreloadAction(PRELOAD_UNDEFINED),
@ -2818,6 +2848,25 @@ private:
bool mPendingNotifyOutput; bool mPendingNotifyOutput;
}; };
class HTMLMediaElement::MediaStreamTracksAvailableCallback:
public DOMMediaStream::OnTracksAvailableCallback
{
public:
explicit MediaStreamTracksAvailableCallback(HTMLMediaElement* aElement,
DOMMediaStream::TrackTypeHints aExpectedTracks = 0):
DOMMediaStream::OnTracksAvailableCallback(aExpectedTracks),
mElement(aElement)
{}
virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
mElement->NotifyMediaStreamTracksAvailable(aStream);
}
private:
HTMLMediaElement* mElement;
};
void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream) void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
{ {
NS_ASSERTION(!mSrcStream && !mSrcStreamListener, "Should have been ended already"); NS_ASSERTION(!mSrcStream && !mSrcStreamListener, "Should have been ended already");
@ -2839,22 +2888,26 @@ void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
if (mPausedForInactiveDocumentOrChannel) { if (mPausedForInactiveDocumentOrChannel) {
GetSrcMediaStream()->ChangeExplicitBlockerCount(1); GetSrcMediaStream()->ChangeExplicitBlockerCount(1);
} }
mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this, DOMMediaStream::HINT_CONTENTS_AUDIO));
mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this, DOMMediaStream::HINT_CONTENTS_VIDEO));
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
ChangeDelayLoadStatus(false); ChangeDelayLoadStatus(false);
GetSrcMediaStream()->AddAudioOutput(this); GetSrcMediaStream()->AddAudioOutput(this);
GetSrcMediaStream()->SetAudioOutputVolume(this, float(mMuted ? 0.0 : mVolume)); SetVolumeInternal();
VideoFrameContainer* container = GetVideoFrameContainer(); VideoFrameContainer* container = GetVideoFrameContainer();
if (container) { if (container) {
GetSrcMediaStream()->AddVideoOutput(container); GetSrcMediaStream()->AddVideoOutput(container);
} }
CheckAutoplayDataReady();
// Note: we must call DisconnectTrackListListeners(...) before dropping // Note: we must call DisconnectTrackListListeners(...) before dropping
// mSrcStream // mSrcStream
mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks()); mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
AddRemoveSelfReference(); AddRemoveSelfReference();
// FirstFrameLoaded() will be called when the stream has current data. // FirstFrameLoaded() will be called when the stream has current data.
} }
@ -2935,6 +2988,11 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
} else { } else {
UpdateMediaSize(aInfo->mVideo.mDisplay); UpdateMediaSize(aInfo->mVideo.mDisplay);
} }
if (IsVideo() && aInfo->HasVideo()) {
// We are a video element playing video so update the screen wakelock
NotifyOwnerDocumentActivityChanged();
}
} }
void HTMLMediaElement::FirstFrameLoaded() void HTMLMediaElement::FirstFrameLoaded()
@ -3195,18 +3253,47 @@ bool HTMLMediaElement::ShouldCheckAllowOrigin()
void HTMLMediaElement::UpdateReadyStateForData(MediaDecoderOwner::NextFrameStatus aNextFrame) void HTMLMediaElement::UpdateReadyStateForData(MediaDecoderOwner::NextFrameStatus aNextFrame)
{ {
if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) { mLastNextFrameStatus = aNextFrame;
if (mDecoder && mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
// aNextFrame might have a next frame because the decoder can advance // aNextFrame might have a next frame because the decoder can advance
// on its own thread before MetadataLoaded gets a chance to run. // on its own thread before MetadataLoaded gets a chance to run.
// The arrival of more data can't change us out of this readyState. // The arrival of more data can't change us out of this readyState.
return; return;
} }
if (mSrcStream && mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
if ((!mHasAudio && !mHasVideo) ||
(IsVideo() && mHasVideo && mMediaSize == nsIntSize(-1, -1))) {
return;
}
// We are playing a stream that has video and a video frame is now set.
// This means we have all metadata needed to change ready state.
MediaInfo mediaInfo;
mediaInfo.mAudio.mHasAudio = mHasAudio;
mediaInfo.mVideo.mHasVideo = mHasVideo;
if (mHasVideo) {
mediaInfo.mVideo.mDisplay = mMediaSize;
}
MetadataLoaded(&mediaInfo, nsAutoPtr<const MetadataTags>(nullptr));
}
if (aNextFrame == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) { if (aNextFrame == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
return; return;
} }
if (IsVideo() && mHasVideo && !IsPlaybackEnded() &&
GetImageContainer() && !GetImageContainer()->HasCurrentImage()) {
// Don't advance if we are playing video, but don't have a video frame.
// Also, if video became available after advancing to HAVE_CURRENT_DATA
// while we are still playing, we need to revert to HAVE_METADATA until
// a video frame is available.
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
return;
}
if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) { if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
// The decoder has signaled that the download has been suspended by the // The decoder has signaled that the download has been suspended by the
// media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
@ -3354,14 +3441,14 @@ void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState)
bool HTMLMediaElement::CanActivateAutoplay() bool HTMLMediaElement::CanActivateAutoplay()
{ {
// For stream inputs, we activate autoplay on HAVE_CURRENT_DATA because // For stream inputs, we activate autoplay on HAVE_NOTHING because
// this element itself might be blocking the stream from making progress by // this element itself might be blocking the stream from making progress by
// being paused. // being paused.
return !mPausedForInactiveDocumentOrChannel && return !mPausedForInactiveDocumentOrChannel &&
mAutoplaying && mAutoplaying &&
mPaused && mPaused &&
((mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) || ((mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
(mSrcStream && mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA)) && mSrcStream) &&
HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) && HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
mAutoplayEnabled && mAutoplayEnabled &&
!IsEditable(); !IsEditable();
@ -3390,24 +3477,14 @@ void HTMLMediaElement::CheckAutoplayDataReady()
VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer()
{ {
// If we have loaded the metadata, and the size of the video is still if (mVideoFrameContainer)
// (-1, -1), the media has no video. Don't go a create a video frame return mVideoFrameContainer;
// container.
if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
mMediaSize == nsIntSize(-1, -1)) {
return nullptr;
}
// Only video frames need an image container. // Only video frames need an image container.
if (!IsVideo()) { if (!IsVideo()) {
return nullptr; return nullptr;
} }
mHasVideo = true;
if (mVideoFrameContainer)
return mVideoFrameContainer;
mVideoFrameContainer = mVideoFrameContainer =
new VideoFrameContainer(this, LayerManager::CreateAsynchronousImageContainer()); new VideoFrameContainer(this, LayerManager::CreateAsynchronousImageContainer());
@ -3520,6 +3597,7 @@ void HTMLMediaElement::NotifyDecoderPrincipalChanged()
void HTMLMediaElement::UpdateMediaSize(nsIntSize size) void HTMLMediaElement::UpdateMediaSize(nsIntSize size)
{ {
mMediaSize = size; mMediaSize = size;
UpdateReadyStateForData(mLastNextFrameStatus);
} }
void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents) void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents)

Просмотреть файл

@ -281,6 +281,11 @@ public:
void NotifyMediaTrackEnabled(MediaTrack* aTrack); void NotifyMediaTrackEnabled(MediaTrack* aTrack);
/**
* Called when tracks become available to the source media stream.
*/
void NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream);
virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE; virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE;
/** /**
@ -612,6 +617,7 @@ protected:
virtual ~HTMLMediaElement(); virtual ~HTMLMediaElement();
class MediaLoadListener; class MediaLoadListener;
class MediaStreamTracksAvailableCallback;
class StreamListener; class StreamListener;
virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE; virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
@ -1036,6 +1042,9 @@ protected:
nsMediaNetworkState mNetworkState; nsMediaNetworkState mNetworkState;
nsMediaReadyState mReadyState; nsMediaReadyState mReadyState;
// Last value passed from codec or stream source to UpdateReadyStateForData.
NextFrameStatus mLastNextFrameStatus;
enum LoadAlgorithmState { enum LoadAlgorithmState {
// No load algorithm instance is waiting for a source to be added to the // No load algorithm instance is waiting for a source to be added to the
// media in order to continue loading. // media in order to continue loading.

Просмотреть файл

@ -45,6 +45,8 @@ function doTest() {
} }
++step; ++step;
}); });
a.play();
b.play();
} }
</script> </script>
</pre> </pre>