Bug 1244768 part 9 - modify the play() method; r?jwwang, bz, alwu
4.8.12.8
https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play
MozReview-Commit-ID: 5r4xOIQwOEr
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3677,122 +3677,199 @@ void HTMLMediaElement::SetPlayedOrSeeked
}
void
HTMLMediaElement::NotifyXPCOMShutdown()
{
ShutdownDecoder();
}
-void
+already_AddRefed<Promise>
HTMLMediaElement::Play(ErrorResult& aRv)
{
if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) {
- // NOTE: for promise-based-play, will return a pending promise here.
MaybeDoLoad();
- return;
- }
-
- PlayInternal(aRv);
+
+ // A blocked media element will be resumed later, so we return a pending
+ // promise which might be resolved/rejected depends on the result of
+ // resuming the blocked media element.
+ RefPtr<Promise> promise = CreateDOMPromise(aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ mPendingPlayPromises.AppendElement(promise);
+ return promise.forget();
+ }
+
+ RefPtr<Promise> promise = PlayInternal(aRv);
UpdateCustomPolicyAfterPlayed();
-}
-
-void
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
HTMLMediaElement::PlayInternal(ErrorResult& aRv)
{
MOZ_ASSERT(!aRv.Failed());
+ // 4.8.12.8
+ // When the play() method on a media element is invoked, the user agent must
+ // run the following steps.
+
+ // 4.8.12.8 - Step 1:
+ // If the media element is not allowed to play, return a promise rejected
+ // with a "NotAllowedError" DOMException and abort these steps.
if (!IsAllowedToPlay()) {
// NOTE: for promise-based-play, will return a rejected promise here.
- return NS_OK;
- }
+ aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ // 4.8.12.8 - Step 2:
+ // If the media element's error attribute is not null and its code
+ // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
+ // rejected with a "NotSupportedError" DOMException and abort these steps.
+ if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
+ aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ // 4.8.12.8 - Step 3:
+ // Let promise be a new promise and append promise to the list of pending
+ // play promises.
+ RefPtr<Promise> promise = CreateDOMPromise(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ mPendingPlayPromises.AppendElement(promise);
// Play was not blocked so assume user interacted with the element.
mHasUserInteraction = true;
StopSuspendingAfterFirstFrame();
SetPlayedOrSeeked(true);
+ // 4.8.12.8 - Step 4:
+ // If the media element's networkState attribute has the value NETWORK_EMPTY,
+ // invoke the media element's resource selection algorithm.
MaybeDoLoad();
if (mSuspendedForPreloadNone) {
ResumeLoad(PRELOAD_ENOUGH);
}
+ // 4.8.12.8 - Step 5:
+ // If the playback has ended and the direction of playback is forwards,
+ // seek to the earliest possible position of the media resource.
+
// Even if we just did Load() or ResumeLoad(), we could already have a decoder
// here if we managed to clone an existing decoder.
if (mDecoder) {
if (mDecoder->IsEnded()) {
SetCurrentTime(0);
}
if (!mPausedForInactiveDocumentOrChannel) {
nsresult rv = mDecoder->Play();
if (NS_FAILED(rv)) {
+ // We don't need to remove the _promise_ from _mPendingPlayPromises_ here.
+ // If something wrong between |mPendingPlayPromises.AppendElement(promise);|
+ // and here, the _promise_ should already have been rejected. Otherwise,
+ // the _promise_ won't be returned to JS at all, so just leave it in the
+ // _mPendingPlayPromises_ and let it be resolved/rejected with the
+ // following actions and the promise-resolution won't be observed at all.
aRv.Throw(rv);
- return;
+ return nullptr;
}
}
}
if (mCurrentPlayRangeStart == -1.0) {
mCurrentPlayRangeStart = CurrentTime();
}
- bool oldPaused = mPaused;
+ const bool oldPaused = mPaused;
mPaused = false;
mAutoplaying = false;
// We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
// and our preload status.
AddRemoveSelfReference();
UpdatePreloadAction();
UpdateSrcMediaStreamPlaying();
// TODO: If the playback has ended, then the user agent must set
// seek to the effective start.
+
+ // 4.8.12.8 - Step 6:
+ // If the media element's paused attribute is true, run the following steps:
if (oldPaused) {
+ // 6.1. Change the value of paused to false. (Already done.)
+ // This step is uplifted because the "block-media-playback" feature needs
+ // the mPaused to be false before UpdateAudioChannelPlayingState() being
+ // called.
+
+ // 6.2. If the show poster flag is true, set the element's show poster flag
+ // to false and run the time marches on steps.
+
+ // 6.3. Queue a task to fire a simple event named play at the element.
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
+
+ // 6.4. If the media element's readyState attribute has the value
+ // HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
+ // fire a simple event named waiting at the element.
+ // Otherwise, the media element's readyState attribute has the value
+ // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
+ // element.
switch (mReadyState) {
case nsIDOMHTMLMediaElement::HAVE_NOTHING:
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
break;
case nsIDOMHTMLMediaElement::HAVE_METADATA:
case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
FireTimeUpdate(false);
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
break;
case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
FireTimeUpdate(false);
- DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
+ NotifyAboutPlaying();
break;
}
- }
-
- return;
+ } else if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
+ // 7. Otherwise, if the media element's readyState attribute has the value
+ // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
+ // queue a task to resolve pending play promises with the result.
+ AsyncResolvePendingPlayPromises();
+ }
+
+ // 8. Set the media element's autoplaying flag to false. (Already done.)
+
+ // 9. Return promise.
+ return promise.forget();
}
void
HTMLMediaElement::MaybeDoLoad()
{
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
DoLoad();
}
}
NS_IMETHODIMP HTMLMediaElement::Play()
{
if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) {
- // NOTE: for promise-based-play, will return a pending promise here.
MaybeDoLoad();
return NS_OK;
}
ErrorResult rv;
- PlayInternal(rv);
+ RefPtr<Promise> toBeIgnored = PlayInternal(rv);
if (rv.Failed()) {
return rv.StealNSResult();
}
UpdateCustomPolicyAfterPlayed();
return NS_OK;
}
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -534,17 +534,17 @@ public:
return GetBoolAttr(nsGkAtoms::loop);
}
void SetLoop(bool aValue, ErrorResult& aRv)
{
SetHTMLBoolAttr(nsGkAtoms::loop, aValue, aRv);
}
- void Play(ErrorResult& aRv);
+ already_AddRefed<Promise> Play(ErrorResult& aRv);
void Pause(ErrorResult& aRv);
bool Controls() const
{
return GetBoolAttr(nsGkAtoms::controls);
}
@@ -805,18 +805,17 @@ protected:
bool mCapturingDecoder;
bool mCapturingMediaStream;
// The following members are keeping state for a captured MediaStream.
TrackID mNextAvailableTrackID;
nsTArray<Pair<nsString, RefPtr<MediaInputPort>>> mTrackPorts;
};
- // Will make this method return a already_AddRefed<Promise> in next patch.
- void PlayInternal(ErrorResult& aRv);
+ already_AddRefed<Promise> PlayInternal(ErrorResult& aRv);
/** Use this method to change the mReadyState member, so required
* events can be fired.
*/
void ChangeReadyState(nsMediaReadyState aState);
/**
* Use this method to change the mNetworkState member, so required
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -63,17 +63,17 @@ interface HTMLMediaElement : HTMLElement
[NewObject]
readonly attribute TimeRanges seekable;
readonly attribute boolean ended;
[SetterThrows]
attribute boolean autoplay;
[SetterThrows]
attribute boolean loop;
[Throws]
- void play();
+ Promise<void> play();
[Throws]
void pause();
// TODO: Bug 847377 - mediaGroup and MediaController
// media controller
// attribute DOMString mediaGroup;
// attribute MediaController? controller;