Chromium Code Reviews| Index: third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
| diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
| index efeaceaddd63eecdb4b7d2f1a3ec83db944fcc60..34ceb68304e357d06e9e40f2a9c27a76aa56ab07 100644 |
| --- a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
| +++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
| @@ -29,11 +29,11 @@ |
| #include "bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "bindings/core/v8/ScriptController.h" |
| #include "bindings/core/v8/ScriptEventListener.h" |
| +#include "bindings/core/v8/ScriptPromiseResolver.h" |
| #include "core/HTMLNames.h" |
| #include "core/css/MediaList.h" |
| #include "core/dom/Attribute.h" |
| #include "core/dom/ElementTraversal.h" |
| -#include "core/dom/ExceptionCode.h" |
| #include "core/dom/Fullscreen.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/events/Event.h" |
| @@ -72,6 +72,8 @@ |
| #include "platform/MIMETypeFromURL.h" |
| #include "platform/MIMETypeRegistry.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| +#include "platform/Task.h" |
| +#include "platform/ThreadSafeFunctional.h" |
| #include "platform/UserGestureIndicator.h" |
| #include "platform/audio/AudioBus.h" |
| #include "platform/audio/AudioSourceProviderClient.h" |
| @@ -730,6 +732,8 @@ void HTMLMediaElement::prepareForLoad() |
| // one of the task queues, then remove those tasks. |
| cancelPendingEventsAndCallbacks(); |
| + rejectPlayPromises(AbortError, "The play() request was interrupted by a new load request."); |
| + |
| // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue |
| // a task to fire a simple event named abort at the media element. |
| if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) |
| @@ -1244,33 +1248,33 @@ void HTMLMediaElement::noneSupported() |
| m_loadState = WaitingForSource; |
| m_currentSourceNode = nullptr; |
| - // 4.8.10.5 |
| - // 6 - Reaching this step indicates that the media resource failed to load or that the given |
| - // URL could not be resolved. In one atomic operation, run the following steps: |
| + // 4.8.13.5 |
| + // The dedicated media source failure steps are the following steps: |
| - // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to |
| + // 1 - Set the error attribute to a new MediaError object whose code attribute is set to |
| // MEDIA_ERR_SRC_NOT_SUPPORTED. |
| m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED); |
| - // 6.2 - Forget the media element's media-resource-specific text tracks. |
| + // 2 - Forget the media element's media-resource-specific text tracks. |
| forgetResourceSpecificTracks(); |
| - // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value. |
| + // 3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value. |
| setNetworkState(NETWORK_NO_SOURCE); |
| - // 7 - Queue a task to fire a simple event named error at the media element. |
| + // 4 - Set the element's show poster flag to true. |
| + updateDisplayState(); |
| + |
| + // 5 - Fire a simple event named error at the media element. |
| scheduleEvent(EventTypeNames::error); |
| + // 6 - Reject pending play promises with NotSupportedError. |
| + scheduleRejectPlayPromises(NotSupportedError, "Failed to load because no supported source was found."); |
| + |
| closeMediaSource(); |
| - // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
| + // 7 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
| setShouldDelayLoadEvent(false); |
| - // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed, |
| - // the element won't attempt to load another resource. |
| - |
| - updateDisplayState(); |
| - |
| if (layoutObject()) |
| layoutObject()->updateFromElement(); |
| } |
| @@ -1519,7 +1523,7 @@ void HTMLMediaElement::setReadyState(ReadyState state) |
| if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) { |
| scheduleEvent(EventTypeNames::canplay); |
| if (isPotentiallyPlaying) |
| - scheduleEvent(EventTypeNames::playing); |
| + scheduleNotifyPlaying(); |
| shouldUpdateDisplayState = true; |
| } |
| @@ -1527,7 +1531,7 @@ void HTMLMediaElement::setReadyState(ReadyState state) |
| if (oldState <= HAVE_CURRENT_DATA) { |
| scheduleEvent(EventTypeNames::canplay); |
| if (isPotentiallyPlaying) |
| - scheduleEvent(EventTypeNames::playing); |
| + scheduleNotifyPlaying(); |
| } |
| // Check for autoplay, and record metrics about it if needed. |
| @@ -1540,7 +1544,7 @@ void HTMLMediaElement::setReadyState(ReadyState state) |
| m_paused = false; |
| invalidateCachedTime(); |
| scheduleEvent(EventTypeNames::play); |
| - scheduleEvent(EventTypeNames::playing); |
| + scheduleNotifyPlaying(); |
| m_autoplaying = false; |
| } |
| } |
| @@ -1932,7 +1936,32 @@ WebMediaPlayer::Preload HTMLMediaElement::effectivePreloadType() const |
| return autoplay() ? WebMediaPlayer::PreloadAuto : preloadType(); |
| } |
| -void HTMLMediaElement::play() |
| +ScriptPromise HTMLMediaElement::playForBindings(ScriptState* scriptState) |
| +{ |
| + Nullable<ExceptionCode> code = play(); |
| + if (!code.isNull()) { |
| + String message; |
| + switch (code.get()) { |
| + case NotAllowedError: |
| + message = "play() can only be initiated by a user gesture."; |
| + break; |
| + case NotSupportedError: |
| + message = "The element has no supported sources."; |
| + break; |
| + default: |
| + ASSERT_NOT_REACHED(); |
| + } |
| + return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(code.get(), message)); |
| + } |
| + |
| + ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| + ScriptPromise promise = resolver->promise(); |
| + |
| + m_playResolvers.append(resolver); |
| + return promise; |
| +} |
| + |
| +Nullable<ExceptionCode> HTMLMediaElement::play() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::play(%p)", this); |
| @@ -1945,7 +1974,7 @@ void HTMLMediaElement::play() |
| recordAutoplayMetric(PlayMethodFailed); |
| String message = ExceptionMessages::failedToExecute("play", "HTMLMediaElement", "API can only be initiated by a user gesture."); |
| document().addConsoleMessage(ConsoleMessage::create(JSMessageSource, WarningMessageLevel, message)); |
| - return; |
| + return NotAllowedError; |
| } |
| } else if (m_userGestureRequiredForPlay) { |
| if (m_autoplayMediaCounted) |
| @@ -1953,7 +1982,12 @@ void HTMLMediaElement::play() |
| m_userGestureRequiredForPlay = false; |
| } |
| + if (m_error && m_error->code() == MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED) |
| + return NotSupportedError; |
| + |
| playInternal(); |
| + |
| + return nullptr; |
| } |
| void HTMLMediaElement::playInternal() |
| @@ -1984,8 +2018,11 @@ void HTMLMediaElement::playInternal() |
| if (m_readyState <= HAVE_CURRENT_DATA) |
| scheduleEvent(EventTypeNames::waiting); |
| else if (m_readyState >= HAVE_FUTURE_DATA) |
| - scheduleEvent(EventTypeNames::playing); |
| + scheduleNotifyPlaying(); |
| + } else if (m_readyState >= HAVE_FUTURE_DATA) { |
| + scheduleResolvePlayPromises(); |
| } |
| + |
| m_autoplaying = false; |
| updatePlayState(); |
| @@ -2040,6 +2077,7 @@ void HTMLMediaElement::pauseInternal() |
| m_paused = true; |
| scheduleTimeupdateEvent(false); |
| scheduleEvent(EventTypeNames::pause); |
| + scheduleRejectPlayPromises(AbortError, "The play() request was interrupted by a call to pause()."); |
| } |
| updatePlayState(); |
| @@ -3494,6 +3532,7 @@ DEFINE_TRACE(HTMLMediaElement) |
| visitor->trace(m_cueTimeline); |
| visitor->trace(m_textTracks); |
| visitor->trace(m_textTracksWhenResourceSelectionBegan); |
| + visitor->trace(m_playResolvers); |
| visitor->trace(m_audioSourceProvider); |
| visitor->template registerWeakMembers<HTMLMediaElement, &HTMLMediaElement::clearWeakMembers>(this); |
| visitor->trace(m_autoplayHelper); |
| @@ -3571,6 +3610,42 @@ void HTMLMediaElement::triggerAutoplayViewportCheckForTesting() |
| m_autoplayHelper.triggerAutoplayViewportCheckForTesting(); |
| } |
| +void HTMLMediaElement::scheduleResolvePlayPromises() |
| +{ |
| + Platform::current()->currentThread()->taskRunner()->postTask( |
| + BLINK_FROM_HERE, new Task(threadSafeBind(&HTMLMediaElement::resolvePlayPromises, AllowCrossThreadAccess(this)))); |
|
hiroshige
2016/02/18 18:32:41
(1) The code seems single threaded, because schedu
mlamouri (slow - plz ping)
2016/02/18 19:24:42
You are correct, it doesn't need to be thread safe
|
| +} |
| + |
| +void HTMLMediaElement::scheduleRejectPlayPromises(ExceptionCode code, const String& message) |
| +{ |
| + Platform::current()->currentThread()->taskRunner()->postTask( |
| + BLINK_FROM_HERE, new Task(threadSafeBind(&HTMLMediaElement::rejectPlayPromises, AllowCrossThreadAccess(this), code, message))); |
| +} |
| + |
| +void HTMLMediaElement::scheduleNotifyPlaying() |
| +{ |
| + scheduleEvent(EventTypeNames::playing); |
| + scheduleResolvePlayPromises(); |
| +} |
| + |
| +void HTMLMediaElement::resolvePlayPromises() |
| +{ |
| + for (auto& resolver: m_playResolvers) |
| + resolver->resolve(); |
| + |
| + m_playResolvers.clear(); |
| +} |
| + |
| +void HTMLMediaElement::rejectPlayPromises(ExceptionCode code, const String& message) |
| +{ |
| + ASSERT(code == AbortError || code == NotSupportedError); |
| + |
| + for (auto& resolver: m_playResolvers) |
| + resolver->reject(DOMException::create(code, message)); |
| + |
| + m_playResolvers.clear(); |
| +} |
| + |
| void HTMLMediaElement::clearWeakMembers(Visitor* visitor) |
| { |
| if (!Heap::isHeapObjectAlive(m_audioSourceNode)) |