Bug 1276272 - part 3 - implement promise-based HTMLMediaElement::seekToNextFrame(); r=jwwang

MozReview-Commit-ID: JaUQe5GK6bF

--HG--
extra : rebase_source : 63ffb58ca29776757cd869579e149a4330311e08
This commit is contained in:
Kaku Kuo 2016-06-09 20:27:39 +01:00
Родитель 5b76709f3e
Коммит 9ace015fa5
4 изменённых файлов: 110 добавлений и 30 удалений

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

@ -1466,19 +1466,19 @@ HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv)
{
LOG(LogLevel::Debug, ("Reporting telemetry VIDEO_FASTSEEK_USED"));
Telemetry::Accumulate(Telemetry::VIDEO_FASTSEEK_USED, 1);
Seek(aTime, SeekTarget::PrevSyncPoint, aRv);
RefPtr<Promise> tobeDropped = Seek(aTime, SeekTarget::PrevSyncPoint, aRv);
}
void
already_AddRefed<Promise>
HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv)
{
Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
return Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
}
void
HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv)
{
Seek(aCurrentTime, SeekTarget::Accurate, aRv);
RefPtr<Promise> tobeDropped = Seek(aCurrentTime, SeekTarget::Accurate, aRv);
}
/**
@ -1519,7 +1519,7 @@ IsInRanges(dom::TimeRanges& aRanges,
return NS_OK;
}
void
already_AddRefed<Promise>
HTMLMediaElement::Seek(double aTime,
SeekTarget::Type aSeekType,
ErrorResult& aRv)
@ -1527,6 +1527,19 @@ HTMLMediaElement::Seek(double aTime,
// aTime should be non-NaN.
MOZ_ASSERT(!mozilla::IsNaN(aTime));
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(OwnerDoc()->GetInnerWindow());
if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Detect if user has interacted with element by seeking so that
// play will not be blocked when initiated by a script.
if (EventStateManager::IsHandlingUserInput() || nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
@ -1537,7 +1550,8 @@ HTMLMediaElement::Seek(double aTime,
if (mSrcStream) {
// do nothing since media streams have an empty Seekable range.
return;
promise->MaybeRejectWithUndefined();
return promise.forget();
}
if (mPlayed && mCurrentPlayRangeStart != -1.0) {
@ -1554,27 +1568,30 @@ HTMLMediaElement::Seek(double aTime,
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
mDefaultPlaybackStartPosition = aTime;
return;
promise->MaybeRejectWithUndefined();
return promise.forget();
}
if (!mDecoder) {
// mDecoder must always be set in order to reach this point.
NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
return;
promise->MaybeRejectWithUndefined();
return promise.forget();
}
// Clamp the seek target to inside the seekable ranges.
RefPtr<dom::TimeRanges> seekable = new dom::TimeRanges(ToSupports(OwnerDoc()));
media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
if (seekableIntervals.IsInvalid()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
return promise.forget();
}
seekableIntervals.ToTimeRanges(seekable);
uint32_t length = 0;
seekable->GetLength(&length);
if (!length) {
return;
promise->MaybeRejectWithUndefined();
return promise.forget();
}
// If the position we want to seek to is not in a seekable range, we seek
@ -1585,8 +1602,8 @@ HTMLMediaElement::Seek(double aTime,
int32_t range = 0;
bool isInRange = false;
if (NS_FAILED(IsInRanges(*seekable, aTime, isInRange, range))) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
return promise.forget();
}
if (!isInRange) {
if (range != -1) {
@ -1596,11 +1613,11 @@ HTMLMediaElement::Seek(double aTime,
double leftBound, rightBound;
if (NS_FAILED(seekable->End(range, &leftBound))) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
return promise.forget();
}
if (NS_FAILED(seekable->Start(range + 1, &rightBound))) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
return promise.forget();
}
double distanceLeft = Abs(leftBound - aTime);
double distanceRight = Abs(rightBound - aTime);
@ -1615,7 +1632,7 @@ HTMLMediaElement::Seek(double aTime,
// Clamp the seek target to the end of the last seekable range.
if (NS_FAILED(seekable->End(length - 1, &aTime))) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
return promise.forget();
}
}
} else {
@ -1641,13 +1658,15 @@ HTMLMediaElement::Seek(double aTime,
// The media backend is responsible for dispatching the timeupdate
// event if it changes the playback position as a result of the seek.
LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
nsresult rv = mDecoder->Seek(aTime, aSeekType);
nsresult rv = mDecoder->Seek(aTime, aSeekType, promise);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
// We changed whether we're seeking so we need to AddRemoveSelfReference.
AddRemoveSelfReference();
return promise.forget();
}
NS_IMETHODIMP HTMLMediaElement::SetCurrentTime(double aCurrentTime)

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

@ -482,7 +482,7 @@ public:
void FastSeek(double aTime, ErrorResult& aRv);
void SeekToNextFrame(ErrorResult& aRv);
already_AddRefed<Promise> SeekToNextFrame(ErrorResult& aRv);
double Duration() const;
@ -1125,7 +1125,7 @@ protected:
// seek target, or PrevSyncPoint if a quicker but less precise seek is
// desired, and we'll seek to the sync point (keyframe and/or start of the
// next block of audio samples) preceeding seek target.
void Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
// A method to check if we are playing through the AudioChannel.
bool IsPlayingThroughTheAudioChannel() const;

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

@ -25,6 +25,7 @@
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/AudioTrackList.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h"
#include "nsPrintfCString.h"
@ -814,7 +815,7 @@ MediaDecoder::Play()
}
nsresult
MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType, dom::Promise* aPromise /*=nullptr*/)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE(!mShuttingDown, NS_ERROR_FAILURE);
@ -831,7 +832,7 @@ MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
mLogicallySeeking = true;
SeekTarget target = SeekTarget(timeUsecs, aSeekType);
CallSeek(target);
CallSeek(target, aPromise);
if (mPlayState == PLAY_STATE_ENDED) {
PinForSeek();
@ -841,10 +842,48 @@ MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
}
void
MediaDecoder::CallSeek(const SeekTarget& aTarget)
MediaDecoder::AsyncResolveSeekDOMPromiseIfExists()
{
MOZ_ASSERT(NS_IsMainThread());
if (mSeekDOMPromise) {
RefPtr<dom::Promise> promise = mSeekDOMPromise;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
promise->MaybeResolve(JS::UndefinedHandleValue);
});
AbstractThread::MainThread()->Dispatch(r.forget());
mSeekDOMPromise = nullptr;
}
}
void
MediaDecoder::AsyncRejectSeekDOMPromiseIfExists()
{
MOZ_ASSERT(NS_IsMainThread());
if (mSeekDOMPromise) {
RefPtr<dom::Promise> promise = mSeekDOMPromise;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
promise->MaybeRejectWithUndefined();
});
AbstractThread::MainThread()->Dispatch(r.forget());
mSeekDOMPromise = nullptr;
}
}
void
MediaDecoder::DiscardOngoingSeekIfExists()
{
MOZ_ASSERT(NS_IsMainThread());
mSeekRequest.DisconnectIfExists();
AsyncRejectSeekDOMPromiseIfExists();
}
void
MediaDecoder::CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise)
{
MOZ_ASSERT(NS_IsMainThread());
DiscardOngoingSeekIfExists();
mSeekDOMPromise = aPromise;
mSeekRequest.Begin(
mDecoderStateMachine->InvokeSeek(aTarget)
->Then(AbstractThread::MainThread(), __func__, this,
@ -1256,12 +1295,22 @@ MediaDecoder::OnSeekResolved(SeekResolveValue aVal)
if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
mOwner->SeekCompleted();
AsyncResolveSeekDOMPromiseIfExists();
if (fireEnded) {
mOwner->PlaybackEnded();
}
}
}
void
MediaDecoder::OnSeekRejected()
{
MOZ_ASSERT(NS_IsMainThread());
mSeekRequest.Complete();
mLogicallySeeking = false;
AsyncRejectSeekDOMPromiseIfExists();
}
void
MediaDecoder::SeekingStarted(MediaDecoderEventVisibility aEventVisibility)
{

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

@ -43,6 +43,10 @@ class nsIPrincipal;
namespace mozilla {
namespace dom {
class Promise;
}
class VideoFrameContainer;
class MediaDecoderStateMachine;
@ -166,7 +170,8 @@ public:
// Seek to the time position in (seconds) from the start of the video.
// If aDoFastSeek is true, we'll seek to the sync point/keyframe preceeding
// the seek target.
virtual nsresult Seek(double aTime, SeekTarget::Type aSeekType);
virtual nsresult Seek(double aTime, SeekTarget::Type aSeekType,
dom::Promise* aPromise = nullptr);
// Initialize state machine and schedule it.
nsresult InitializeStateMachine();
@ -391,12 +396,7 @@ private:
// Call on the main thread only.
void PlaybackEnded();
void OnSeekRejected()
{
MOZ_ASSERT(NS_IsMainThread());
mSeekRequest.Complete();
mLogicallySeeking = false;
}
void OnSeekRejected();
void OnSeekResolved(SeekResolveValue aVal);
void SeekingChanged()
@ -620,12 +620,24 @@ private:
#endif
protected:
virtual void CallSeek(const SeekTarget& aTarget);
// The promise resolving/rejection is queued as a "micro-task" which will be
// handled immediately after the current JS task and before any pending JS
// tasks.
// At the time we are going to resolve/reject a promise, the "seeking" event
// task should already be queued but might yet be processed, so we queue one
// more task to file the promise resolving/rejection micro-tasks
// asynchronously to make sure that the micro-tasks are processed after the
// "seeking" event task.
void AsyncResolveSeekDOMPromiseIfExists();
void AsyncRejectSeekDOMPromiseIfExists();
void DiscardOngoingSeekIfExists();
virtual void CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise);
// Returns true if heuristic dormant is supported.
bool IsHeuristicDormantSupported() const;
MozPromiseRequestHolder<SeekPromise> mSeekRequest;
RefPtr<dom::Promise> mSeekDOMPromise;
// True when seeking or otherwise moving the play position around in
// such a manner that progress event data is inaccurate. This is set