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

Unified Diff: third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp

Issue 1370723002: Include viewport visibility checks for autoplay experiment. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebased. Created 5 years, 1 month 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: third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
diff --git a/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp b/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
index 3aa5811dc2c6c232404f8dea9fbf040b989ce8f1..efacfc60689c597d98367ad783bed465bbeead12 100644
--- a/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
+++ b/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
@@ -6,6 +6,7 @@
#include "core/html/AutoplayExperimentHelper.h"
#include "core/dom/Document.h"
+#include "core/frame/FrameView.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLMediaElement.h"
#include "core/layout/LayoutBox.h"
@@ -21,15 +22,22 @@ namespace blink {
using namespace HTMLNames;
+// Seconds to wait after a video has stopped moving before playing it.
+static const double kViewportTimerPollDelay = 0.5;
+
AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
: m_element(element)
- , m_mode(AutoplayExperimentConfig::Mode::Off)
+ , m_mode(Mode::ExperimentOff)
, m_playPending(false)
+ , m_registeredWithLayoutObject(false)
+ , m_wasInViewport(false)
+ , m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity())
+ , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
{
if (document().settings()) {
- m_mode = AutoplayExperimentConfig::fromString(document().settings()->autoplayExperimentMode());
+ m_mode = fromString(document().settings()->autoplayExperimentMode());
- if (m_mode != AutoplayExperimentConfig::Mode::Off) {
+ if (m_mode != Mode::ExperimentOff) {
WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d",
m_mode);
}
@@ -38,14 +46,19 @@ AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
AutoplayExperimentHelper::~AutoplayExperimentHelper()
{
+ unregisterForPositionUpdatesIfNeeded();
}
void AutoplayExperimentHelper::becameReadyToPlay()
{
// Assuming that we're eligible to override the user gesture requirement,
- // then play.
+ // either play if we meet the visibility checks, or install a listener
+ // to wait for them to pass.
if (isEligible()) {
- prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
+ if (meetsVisibilityRequirements())
+ prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
+ else
+ registerForPositionUpdatesIfNeeded();
}
}
@@ -61,11 +74,19 @@ void AutoplayExperimentHelper::playMethodCalled()
if (isEligible()) {
// Remember that userGestureRequiredForPlay is required for
// us to be eligible for the experiment.
- // We are able to override the gesture requirement now, so
- // do so.
- prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately);
+ // If we are able to override the gesture requirement now, then
+ // do so. Otherwise, install an event listener if we need one.
+ if (meetsVisibilityRequirements()) {
+ // Override the gesture and play.
+ prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately);
+ } else {
+ // Wait for viewport visibility.
+ registerForPositionUpdatesIfNeeded();
+ }
}
+ } else if (m_element.isUserGestureRequiredForPlay()) {
+ unregisterForPositionUpdatesIfNeeded();
}
}
@@ -73,18 +94,163 @@ void AutoplayExperimentHelper::pauseMethodCalled()
{
// Don't try to autoplay, if we would have.
m_playPending = false;
+ unregisterForPositionUpdatesIfNeeded();
}
void AutoplayExperimentHelper::mutedChanged()
{
- // In other words, start playing if we just needed 'mute' to autoplay.
+ // If we are no longer eligible for the autoplay experiment, then also
+ // quit listening for 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 (!isEligible()) {
+ unregisterForPositionUpdatesIfNeeded();
+ } else {
+ // Try to play. If we can't, then install a listener.
+ if (!maybeStartPlaying())
+ registerForPositionUpdatesIfNeeded();
+ }
+}
+
+void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded()
+{
+ // If we don't require that the player is in the viewport, then we don't
+ // need the listener.
+ if (!enabled(IfViewport)) {
+ if (!enabled(IfPageVisible))
+ return;
+ }
+
+ if (LayoutObject* layoutObject = m_element.layoutObject()) {
+ LayoutMedia* layoutMedia = toLayoutMedia(layoutObject);
+ layoutMedia->setRequestPositionUpdates(true);
+ }
+
+ // Set this unconditionally, in case we have no layout object yet.
+ m_registeredWithLayoutObject = true;
+}
+
+void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded()
+{
+ if (m_registeredWithLayoutObject) {
+ if (LayoutObject* obj = m_element.layoutObject()) {
+ LayoutMedia* layoutMedia = toLayoutMedia(obj);
+ layoutMedia->setRequestPositionUpdates(false);
+ }
+ }
+
+ // Clear this unconditionally so that we don't re-register if we didn't
+ // have a LayoutObject now, but get one later.
+ m_registeredWithLayoutObject = false;
+}
+
+void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect)
+{
+ // Something, maybe position, has changed. If applicable, start a
+ // timer to look for the end of a scroll operation.
+ // Don't do much work here.
+ // Also note that we are called quite often, including when the
+ // page becomes visible. That's why we don't bother to register
+ // for page visibility changes explicitly.
+
+ m_lastVisibleRect = visibleRect;
+
+ if (!m_element.layoutObject())
+ return;
+
+ IntRect currentLocation = m_element.layoutObject()->absoluteBoundingBoxRect();
+ bool inViewport = meetsVisibilityRequirements();
+
+ if (m_lastLocation != currentLocation) {
+ m_lastLocationUpdateTime = monotonicallyIncreasingTime();
+ m_lastLocation = currentLocation;
+ }
+
+ if (inViewport && !m_wasInViewport) {
+ // Only reset the timer when we transition from not visible to
+ // visible, because resetting the timer isn't cheap.
+ m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE);
+ }
+ m_wasInViewport = inViewport;
+}
+
+void AutoplayExperimentHelper::updatePositionNotificationRegistration()
+{
+ if (m_registeredWithLayoutObject) {
+ LayoutMedia* layoutMedia = toLayoutMedia(m_element.layoutObject());
+ layoutMedia->setRequestPositionUpdates(true);
+ }
+}
+
+void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting()
+{
+ FrameView* view = document().view();
+ if (view)
+ positionChanged(view->rootFrameToContents(view->computeVisibleArea()));
+
+ // Make sure that the last update appears to be sufficiently far in the
+ // past to appear that scrolling has stopped by now in viewportTimerFired.
+ m_lastLocationUpdateTime = monotonicallyIncreasingTime() - kViewportTimerPollDelay - 1;
+ viewportTimerFired(nullptr);
+}
+
+void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper>*)
+{
+ double now = monotonicallyIncreasingTime();
+ double delta = now - m_lastLocationUpdateTime;
+ if (delta < kViewportTimerPollDelay) {
+ // If we are not visible, then skip the timer. It will be started
+ // again if we become visible again.
+ if (m_wasInViewport)
+ m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta, BLINK_FROM_HERE);
+
+ return;
+ }
+
+ // Sufficient time has passed since the last scroll that we'll
+ // treat it as the end of scroll. Autoplay if we should.
maybeStartPlaying();
}
+bool AutoplayExperimentHelper::meetsVisibilityRequirements() const
+{
+ if (enabled(IfPageVisible)
+ && m_element.document().pageVisibilityState() != PageVisibilityStateVisible)
+ return false;
+
+ if (!enabled(IfViewport))
+ return true;
+
+ if (m_lastVisibleRect.isEmpty())
+ return false;
+
+ LayoutObject* layoutObject = m_element.layoutObject();
+ if (!layoutObject)
+ return false;
+
+ IntRect currentLocation = layoutObject->absoluteBoundingBoxRect();
+
+ // If element completely fills the screen, then truncate it to exactly
+ // match the screen. Any element that is wider just has to cover.
+ if (currentLocation.x() <= m_lastVisibleRect.x()
+ && currentLocation.x() + currentLocation.width() >= m_lastVisibleRect.x() + m_lastVisibleRect.width()) {
+ currentLocation.setX(m_lastVisibleRect.x());
+ currentLocation.setWidth(m_lastVisibleRect.width());
+ }
+
+ if (currentLocation.y() <= m_lastVisibleRect.y()
+ && currentLocation.y() + currentLocation.height() >= m_lastVisibleRect.y() + m_lastVisibleRect.height()) {
+ currentLocation.setY(m_lastVisibleRect.y());
+ currentLocation.setHeight(m_lastVisibleRect.height());
+ }
+
+ return m_lastVisibleRect.contains(currentLocation);
+}
+
bool AutoplayExperimentHelper::maybeStartPlaying()
{
// See if we're allowed to autoplay now.
- if (!isEligible()) {
+ if (!isEligible() || !meetsVisibilityRequirements()) {
return false;
}
@@ -99,6 +265,9 @@ bool AutoplayExperimentHelper::maybeStartPlaying()
bool AutoplayExperimentHelper::isEligible() const
{
+ if (m_mode == Mode::ExperimentOff)
+ return false;
+
// 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
@@ -106,46 +275,38 @@ bool AutoplayExperimentHelper::isEligible() const
if (!m_element.isUserGestureRequiredForPlay())
return false;
- if (m_mode == AutoplayExperimentConfig::Mode::Off)
- return false;
-
// Make sure that this is an element of the right type.
- if (!enabled(AutoplayExperimentConfig::Mode::ForVideo)
+ if (!enabled(ForVideo)
&& isHTMLVideoElement(m_element))
return false;
- if (!enabled(AutoplayExperimentConfig::Mode::ForAudio)
+ if (!enabled(ForAudio)
&& isHTMLAudioElement(m_element))
return false;
// If nobody has requested playback, either by the autoplay attribute or
// a play() call, then do nothing.
- if (!m_playPending && !m_element.shouldAutoplay())
- 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_element.paused())
+ if (!m_playPending && !m_element.shouldAutoplay())
return false;
// Note that the viewport test always returns false on desktop, which is
// why video-autoplay-experiment.html doesn't check -ifmobile .
- if (enabled(AutoplayExperimentConfig::Mode::IfMobile)
+ if (enabled(IfMobile)
&& !document().viewportDescription().isLegacyViewportType())
return false;
- // If media is muted, then autoplay when it comes into view.
- if (enabled(AutoplayExperimentConfig::Mode::IfMuted))
+ // If we require muted media and this is muted, then it is eligible.
+ if (enabled(IfMuted))
return m_element.muted();
- // Autoplay when it comes into view (if needed), maybe muted.
+ // Element is eligible for gesture override, maybe muted.
return true;
}
void AutoplayExperimentHelper::muteIfNeeded()
{
- if (enabled(AutoplayExperimentConfig::Mode::PlayMuted)) {
+ if (enabled(PlayMuted)) {
ASSERT(!isEligible());
// If we are actually changing the muted state, then this will call
// mutedChanged(). If isEligible(), then mutedChanged() will try
@@ -162,6 +323,7 @@ void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
// once. Be sure to do this before muteIfNeeded().
m_element.removeUserGestureRequirement();
+ unregisterForPositionUpdatesIfNeeded();
muteIfNeeded();
// Record that this autoplayed without a user gesture. This is normally
@@ -177,4 +339,25 @@ Document& AutoplayExperimentHelper::document() const
return m_element.document();
}
+AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(const String& mode)
+{
+ Mode value = ExperimentOff;
+ if (mode.contains("-forvideo"))
+ value |= ForVideo;
+ if (mode.contains("-foraudio"))
+ value |= ForAudio;
+ if (mode.contains("-ifpagevisible"))
+ value |= IfPageVisible;
+ if (mode.contains("-ifviewport"))
+ value |= IfViewport;
+ if (mode.contains("-ifmuted"))
+ value |= IfMuted;
+ if (mode.contains("-ifmobile"))
+ value |= IfMobile;
+ if (mode.contains("-playmuted"))
+ value |= PlayMuted;
+
+ return value;
+}
+
}
« no previous file with comments | « third_party/WebKit/Source/core/html/AutoplayExperimentHelper.h ('k') | third_party/WebKit/Source/core/html/HTMLMediaElement.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698