Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(450)

Unified Diff: Source/core/html/HTMLMediaElement.cpp

Issue 1179223002: Implement autoplay gesture override experiment. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: removed WebRuntimeFeatures.h whitespace-only change. Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/core/html/HTMLMediaElement.cpp
diff --git a/Source/core/html/HTMLMediaElement.cpp b/Source/core/html/HTMLMediaElement.cpp
index 0136464ef89e731646dcb0d6fced717c57badea3..7c5e9ed022cf5d5f5ace58a58dcede41924cb7e6 100644
--- a/Source/core/html/HTMLMediaElement.cpp
+++ b/Source/core/html/HTMLMediaElement.cpp
@@ -249,25 +249,7 @@ static bool canLoadURL(const KURL& url, const ContentType& contentType, const St
return false;
}
-// These values are used for a histogram. Do not reorder.
-enum AutoplayMetrics {
- // Media element with autoplay seen.
- AutoplayMediaFound = 0,
- // Autoplay enabled and user stopped media play at any point.
- AutoplayStopped = 1,
- // Autoplay enabled but user bailed out on media play early.
- AutoplayBailout = 2,
- // Autoplay disabled but user manually started media.
- AutoplayManualStart = 3,
- // Autoplay was (re)enabled through a user-gesture triggered load()
- AutoplayEnabledThroughLoad = 4,
- // Autoplay disabled by sandbox flags.
- AutoplayDisabledBySandbox = 5,
- // This enum value must be last.
- NumberOfAutoplayMetrics,
-};
-
-static void recordAutoplayMetric(AutoplayMetrics metric)
+void HTMLMediaElement::recordAutoplayMetric(AutoplayMetrics metric)
{
Platform::current()->histogramEnumeration("Blink.MediaElement.Autoplay", metric, NumberOfAutoplayMetrics);
}
@@ -298,6 +280,24 @@ WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType&
URLRegistry* HTMLMediaElement::s_mediaStreamRegistry = 0;
+class HTMLMediaElement::AutoplayExperimentScrollListener : public EventListener {
+ public:
+ AutoplayExperimentScrollListener(HTMLMediaElement* element) : EventListener(CPPEventListenerType), m_element(element) { }
+ virtual bool operator==(const EventListener& them)
+ {
+ return &them == this;
+ }
+
+ void handleEvent(ExecutionContext*, Event*) override
+ {
+ if (m_element)
+ m_element->autoplayExperimentMaybeStartPlaying();
+ }
+
+ private:
+ HTMLMediaElement* m_element;
+};
+
void HTMLMediaElement::setMediaStreamRegistry(URLRegistry* registry)
{
ASSERT(!s_mediaStreamRegistry);
@@ -359,6 +359,9 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
, m_isFinalizing(false)
, m_initialPlayWithoutUserGestures(false)
, m_autoplayMediaCounted(false)
+ , m_autoplayExperimentPlayPending(false)
+ , m_autoplayExperimentStartedByExperiment(false)
+ , m_autoplayExperimentMode(ExperimentOff)
, m_audioTracks(AudioTrackList::create(*this))
, m_videoTracks(VideoTrackList::create(*this))
, m_textTracks(nullptr)
@@ -376,6 +379,22 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
if (document.settings() && document.settings()->mediaPlaybackRequiresUserGesture())
m_userGestureRequiredForPlay = true;
+ if (document.settings()) {
+ const String& autoplayMode = document.settings()->autoplayExperimentMode();
+ if (autoplayMode == "always") {
+ m_autoplayExperimentMode = ExperimentAlways;
+ } else if (autoplayMode == "if-muted") {
+ m_autoplayExperimentMode = ExperimentIfMuted;
+ } else if (autoplayMode == "play-muted") {
+ m_autoplayExperimentMode = ExperimentPlayMuted;
+ }
+
+ if (m_autoplayExperimentMode != ExperimentOff) {
+ WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to '%s'",
+ autoplayMode.ascii().data());
+ }
+ }
+
setHasCustomStyleCallbacks();
addElementToDocumentMap(this, &document);
}
@@ -383,6 +402,9 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
HTMLMediaElement::~HTMLMediaElement()
{
WTF_LOG(Media, "HTMLMediaElement::~HTMLMediaElement(%p)", this);
+
+ autoplayExperimentClearScrollListenerIfNeeded();
+
#if !ENABLE(OILPAN)
// HTMLMediaElement and m_asyncEventQueue always become unreachable
// together. So HTMLMediaElement and m_asyncEventQueue are destructed in
@@ -1523,11 +1545,25 @@ void HTMLMediaElement::setReadyState(ReadyState state)
if (document().isSandboxed(SandboxAutomaticFeatures)) {
recordAutoplayMetric(AutoplayDisabledBySandbox);
- } else if (!m_userGestureRequiredForPlay) {
- m_paused = false;
- invalidateCachedTime();
- scheduleEvent(EventTypeNames::play);
- scheduleEvent(EventTypeNames::playing);
+ } else {
+ // If the autoplay experiment says that it's okay to play now,
+ // then don't require a user gesture.
+ if (autoplayExperimentIsEligible()) {
+ if (autoplayExperimentIsVisible()) {
+ autoplayExperimentPrepareToPlay(AutoplayExperimentStartedByLoad);
+ // Will play below.
+ } else {
+ // Wait for visibility checks to pass.
+ autoplayExperimentInstallScrollListenerIfNeeded();
+ }
+ }
+
+ if (!m_userGestureRequiredForPlay) {
+ m_paused = false;
+ invalidateCachedTime();
+ scheduleEvent(EventTypeNames::play);
+ scheduleEvent(EventTypeNames::playing);
+ }
}
}
@@ -1926,8 +1962,28 @@ void HTMLMediaElement::play()
{
WTF_LOG(Media, "HTMLMediaElement::play(%p)", this);
+ // Set the pending state, even if the play isn't going to be pending.
+ // Eligibility can change if, for example, the mute status changes.
+ // Having this set is okay.
+ m_autoplayExperimentPlayPending = true;
+
if (!UserGestureIndicator::processingUserGesture()) {
autoplayMediaEncountered();
+
+ if (autoplayExperimentIsEligible()) {
+ // Remember that m_userGestureRequiredForPlay is required for
+ // us to be eligible for the experiment.
+ // If we are able to override the gesture requirement now, then
+ // do so. Otherwise, install a scroll listener if we need one.
+ if (autoplayExperimentIsVisible()) {
+ // Override the gesture and play.
+ autoplayExperimentPrepareToPlay(AutoplayExperimentStartedByPlay);
+ } else {
+ // Wait for visibility.
+ autoplayExperimentInstallScrollListenerIfNeeded();
+ }
+ }
+
if (m_userGestureRequiredForPlay) {
String message = ExceptionMessages::failedToExecute("play", "HTMLMediaElement", "API can only be initiated by a user gesture.");
document().executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, WarningMessageLevel, message));
@@ -1937,6 +1993,7 @@ void HTMLMediaElement::play()
if (m_autoplayMediaCounted)
recordAutoplayMetric(AutoplayManualStart);
m_userGestureRequiredForPlay = false;
+ autoplayExperimentClearScrollListenerIfNeeded();
}
playInternal();
@@ -1993,13 +2050,21 @@ void HTMLMediaElement::gesturelessInitialPlayHalted()
recordAutoplayMetric(AutoplayStopped);
+ if (m_autoplayExperimentStartedByExperiment)
+ recordAutoplayMetric(AutoplayExperimentStopped);
+
// We count the user as having bailed-out on the video if they watched
// less than one minute and less than 50% of it.
double playedTime = currentTime();
if (playedTime < 60) {
double progress = playedTime / duration();
- if (progress < 0.5)
+ if (progress < 0.5) {
recordAutoplayMetric(AutoplayBailout);
+ // If this autoplay was started by the experiment, then record
+ // that separately.
+ if (m_autoplayExperimentStartedByExperiment)
+ recordAutoplayMetric(AutoplayExperimentBailout);
+ }
}
}
@@ -2010,6 +2075,10 @@ void HTMLMediaElement::pause()
if (!m_player || m_networkState == NETWORK_EMPTY)
scheduleDelayedAction(LoadMediaResource);
+ // Don't try to autoplay, if we would have.
+ m_autoplayExperimentPlayPending = false;
+ autoplayExperimentClearScrollListenerIfNeeded();
+
m_autoplaying = false;
if (!m_paused) {
@@ -2107,6 +2176,18 @@ void HTMLMediaElement::setMuted(bool muted)
m_muted = muted;
+ // If we are no longer eligible for the autoplay experiment, then also
+ // quit listening to scroll events. If we are eligible, and if we should
+ // be playing, then start playing. In other words, start playing if
+ // we just needed 'mute' to autoplay.
+ if (!autoplayExperimentIsEligible()) {
+ autoplayExperimentClearScrollListenerIfNeeded();
+ } else {
+ // Try to play. If we can't, then install a visibility listener.
+ if (!autoplayExperimentMaybeStartPlaying())
+ autoplayExperimentInstallScrollListenerIfNeeded();
+ }
+
updateVolume();
scheduleEvent(EventTypeNames::volumechange);
@@ -3711,4 +3792,125 @@ void HTMLMediaElement::clearWeakMembers(Visitor* visitor)
}
#endif
+void HTMLMediaElement::autoplayExperimentInstallScrollListenerIfNeeded()
+{
+ if (document().domWindow() && !m_autoplayExperimentScrollListener) {
+ m_autoplayExperimentScrollListener = adoptRef(new AutoplayExperimentScrollListener(this));
+ document().domWindow()->addEventListener("scroll", m_autoplayExperimentScrollListener, false);
philipj_slow 2015/07/21 12:51:29 I take it this will prevent smooth scrolling while
liberato (no reviews please) 2015/07/27 06:03:09 good point. we really only care if it ends up in
Rick Byers 2015/08/05 14:27:59 Listening to scroll events doesn't "prevent smooth
philipj_slow 2015/08/05 14:40:12 Oh... http://rbyers.github.io/EventListenerOptions
+ }
+}
+
+void HTMLMediaElement::autoplayExperimentClearScrollListenerIfNeeded()
+{
+ if (m_autoplayExperimentScrollListener) {
+ LocalDOMWindow* domWindow = document().domWindow();
+ if (domWindow) {
+ domWindow->removeEventListener("scroll", m_autoplayExperimentScrollListener, false);
+ }
+ // Either way, clear our ref.
+ m_autoplayExperimentScrollListener.clear();
+ }
+}
+
+bool HTMLMediaElement::autoplayExperimentIsVisible()
+{
+ // We could check for eligibility here, but we skip it. Some of our
+ // callers need to do it separately, and we don't want to check more
+ // than we need to.
+
+ // Autoplay is requested, and we're willing to override if the visibility
+ // requirements are met.
+
+ // Check visibility.
+ const LocalDOMWindow* domWindow = document().domWindow();
+ if (!domWindow)
+ return false;
+
+ FloatRect us(offsetLeft(), offsetTop(), clientWidth(), clientHeight());
+ FloatRect screen(domWindow->scrollX(), domWindow->scrollY(), domWindow->innerWidth(), domWindow->innerHeight());
+
+ return screen.contains(us);
+}
+
+bool HTMLMediaElement::autoplayExperimentMaybeStartPlaying()
+{
+ // Make sure that we're eligible and visible.
+ if (!autoplayExperimentIsEligible() || !autoplayExperimentIsVisible()) {
+ return false;
+ }
+
+ // Start playing!
+ autoplayExperimentPrepareToPlay(AutoplayExperimentStartedByScroll);
+ // Why are we always preparing? Just go!
+ playInternal();
+
+ return true;
+}
+
+bool HTMLMediaElement::autoplayExperimentIsEligible() const
+{
+ // If no user gesture is required, then the experiment doesn't apply.
+ // This is what prevents us from starting playback more than once.
+ // Since this flag is never set to true once it's cleared, it will block
+ // the autoplay experiment forever.
+ if (!m_userGestureRequiredForPlay)
+ return false;
+
+ if (m_autoplayExperimentMode == ExperimentOff)
+ return false;
+
+ // If nobody has requested playback, either by the autoplay attribute or
+ // a play() call, then do nothing.
+ if (!m_autoplayExperimentPlayPending && !autoplay())
+ return false;
+
+ // If the video is already playing, then do nothing. Note that there
+ // is not a path where a user gesture is required but the video is
+ // playing. However, we check for completeness.
+ if (!m_paused)
+ return false;
+
+ // Note that the viewport test always returns false on desktop. For
+ // tests, we allow an override.
+ const bool optimizedForMobile = document().viewportDescription().isLegacyViewportType()
+ || (document().settings() && document().settings()->overrideOptimizedForMobileCheck());
+
+ if (!optimizedForMobile)
+ return false;
+
+ if (m_autoplayExperimentMode == ExperimentIfMuted) {
+ // If media is muted, then autoplay when it comes into view.
+ return fastHasAttribute(mutedAttr) || m_muted;
+ }
+
+ // Autoplay when it comes into view, maybe muted.
+ return true;
+}
+
+void HTMLMediaElement::autoplayExperimentMuteIfNeeded()
+{
+ if (m_autoplayExperimentMode == ExperimentPlayMuted) {
+ m_muted = true;
+ updateVolume();
+ }
+}
+
+void HTMLMediaElement::autoplayExperimentPrepareToPlay(AutoplayMetrics metric)
+{
+ recordAutoplayMetric(metric);
+
+ // This also causes !autoplayExperimentIsEligible, so that we don't
+ // allow autoplay more than once.
+ m_userGestureRequiredForPlay = false;
+
+ m_autoplayExperimentStartedByExperiment = true;
+ autoplayExperimentClearScrollListenerIfNeeded();
+ autoplayExperimentMuteIfNeeded();
+
+ // Record that this autoplayed without a user gesture. This is normally
+ // set when we discover an autoplay attribute, but we include all cases
+ // where playback started without a user gesture, e.g., play().
+ m_initialPlayWithoutUserGestures = true;
+}
+
}

Powered by Google App Engine
This is Rietveld 408576698