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

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

Issue 1179223002: Implement autoplay gesture override experiment. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Refactored into AutoplayExperimentHelper. Created 5 years, 4 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/AutoplayExperimentHelper.cpp
diff --git a/Source/core/html/AutoplayExperimentHelper.cpp b/Source/core/html/AutoplayExperimentHelper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9906f7c6a48fdc87e8529b11c2ea9645c7160bc0
--- /dev/null
+++ b/Source/core/html/AutoplayExperimentHelper.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2015 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "core/html/AutoplayExperimentHelper.h"
+
+#include "core/dom/Document.h"
+#include "core/frame/Settings.h"
+#include "core/html/HTMLMediaElement.h"
+#include "platform/UserGestureIndicator.h"
+#include "platform/geometry/FloatRect.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+// How long do we wait after a scroll event before deciding that no more
+// scroll events are going to arrive?
+static const double viewportTimerPollDelay = 0.5;
+
+// Event listener that just informs the AutoplayExperimentHelper that a
+// scroll has happened.
+class AutoplayExperimentHelper::ScrollListener : public EventListener {
+ public:
+ ScrollListener(AutoplayExperimentHelper* helper) : EventListener(CPPEventListenerType), m_helper(helper) { }
+ virtual bool operator==(const EventListener& them)
+ {
+ return &them == this;
+ }
+
+ void handleEvent(ExecutionContext*, Event*) override
+ {
+ if (m_helper)
+ m_helper->notifyScrolled();
+ }
+
+ private:
+ AutoplayExperimentHelper* m_helper;
philipj_slow 2015/08/13 10:15:40 It looks like the pointer is always passed and nev
liberato (no reviews please) 2015/09/01 06:54:19 indeed, but the listener has since been removed.
+};
+
+
+AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
+ : m_element(element)
+ , m_mode(ExperimentOff)
+ , m_playPending(false)
+ , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
+{
+ if (document().settings()) {
+ const String& autoplayMode = document().settings()->autoplayExperimentMode();
ojan 2015/08/11 02:45:21 Did you also want to measure the case Philip sugge
ojan 2015/08/11 02:49:34 Also, I don't see anything for tab visibility. So,
liberato (no reviews please) 2015/09/01 06:54:19 added a check for page()->visibilityState() to isI
philipj_slow 2015/09/02 09:31:47 What about the experiment to only require page vis
philipj_slow 2015/09/04 08:44:08 I haven't seen the other CL yet, so just a gentle
+ if (autoplayMode.contains("enabled")) {
+ // Autoplay with no gesture requirement.
+ m_mode |= ExperimentEnabled;
+ }
+ if (autoplayMode.contains("-ifviewport")) {
+ // Override gesture requirement only if the player is within the
+ // current viewport.
+ m_mode |= ExperimentIfViewport;
+ }
+ if (autoplayMode.contains("-ifmuted")) {
+ // Override gesture requirement only if the media is muted or has
+ // no audio track.
+ m_mode |= ExperimentIfMuted;
+ }
+ if (autoplayMode.contains("-ifmobile")) {
+ // Override gesture requirement only if the page is optimized
+ // for mobile.
+ m_mode |= ExperimentIfMobile;
+ }
+ if (autoplayMode.contains("-playmuted")) {
ojan 2015/08/11 02:45:21 For good measure, add a comment explaining this on
liberato (no reviews please) 2015/09/01 06:54:19 done, though the docs are with the enums in Autopl
+ m_mode |= ExperimentPlayMuted;
+ }
+
+ if (m_mode != ExperimentOff) {
+ WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to '%s' (%d)",
+ autoplayMode.ascii().data(), m_mode);
+ }
+ }
+}
+
+AutoplayExperimentHelper::~AutoplayExperimentHelper()
+{
+ clearEventListenerIfNeeded();
+}
+
+void AutoplayExperimentHelper::onReadyToPlay()
+{
+ // Assuming that we're eligible to override the user gesture requirement,
+ // either play if we meet the visibility checks, or install a listener
+ // to wait for them to pass.
+ if (isEligible()) {
+ if (isInViewportIfNeeded())
+ prepareToPlay(GesturelessPlaybackStartedByLoad);
+ else
+ installEventListenerIfNeeded();
+ }
+}
+
+void AutoplayExperimentHelper::onPlayMethodCalled()
+{
+ // 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_playPending = true;
+
+ if (!UserGestureIndicator::processingUserGesture()) {
+
+ if (isEligible()) {
+ // Remember that 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 an event listener if we need one.
+ if (isInViewportIfNeeded()) {
+ // Override the gesture and play.
+ prepareToPlay(GesturelessPlaybackStartedByPlayMethod);
+ } else {
+ // Wait for viewport visibility.
+ installEventListenerIfNeeded();
+ }
+ }
+
+ } else if (m_element.isUserGestureRequiredForPlay()) {
+ clearEventListenerIfNeeded();
+ }
+}
+
+void AutoplayExperimentHelper::onPauseMethodCalled()
+{
+ // Don't try to autoplay, if we would have.
+ m_playPending = false;
+ clearEventListenerIfNeeded();
+}
+
+void AutoplayExperimentHelper::onMuteChanged()
+{
+ // 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()) {
+ clearEventListenerIfNeeded();
+ } else {
+ // Try to play. If we can't, then install a listener.
+ if (!maybeStartPlaying())
+ installEventListenerIfNeeded();
+ }
+}
+
+void AutoplayExperimentHelper::installEventListenerIfNeeded()
+{
+ // If we don't require that the player is in the viewport, then we don't
+ // need the listener.
+ if (!(m_mode & ExperimentIfViewport))
+ return;
+
+ if (document().domWindow() && !m_scrollListener) {
+ m_scrollListener = adoptRef(new ScrollListener(this));
+ document().domWindow()->addEventListener("scroll", m_scrollListener, false);
+ }
+}
+
+void AutoplayExperimentHelper::clearEventListenerIfNeeded()
+{
+ if (m_scrollListener) {
+ LocalDOMWindow* domWindow = document().domWindow();
+ if (domWindow) {
+ domWindow->removeEventListener("scroll", m_scrollListener, false);
+ }
+ // Either way, clear our ref.
+ m_scrollListener.clear();
+ }
+}
+
+void AutoplayExperimentHelper::notifyScrolled()
+{
+ // Reset the timer to indicate that scrolling has happened
+ // recently, and might still be ongoing.
+ m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE);
+}
+
+void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper>*)
+{
+ // 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::isInViewportIfNeeded()
+{
+ // 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.
+ // Also remember that page visibility is assumed for clank.
+
+ // If viewport visibility isn't required, then it's visible enough.
+ if (!(m_mode & ExperimentIfViewport))
+ return true;
+
+ // Check if we're in the viewport.
+ const LocalDOMWindow* domWindow = document().domWindow();
+ if (!domWindow)
+ return false;
+
+ FloatRect us(m_element.offsetLeft(), m_element.offsetTop(), m_element.clientWidth(), m_element.clientHeight());
+ FloatRect screen(domWindow->scrollX(), domWindow->scrollY(), domWindow->innerWidth(), domWindow->innerHeight());
+
+ return screen.contains(us);
ojan 2015/08/11 02:45:21 What if the video is larger than the screen or par
liberato (no reviews please) 2015/09/01 06:54:19 for those videos that don't show controls, or use
+}
+
+bool AutoplayExperimentHelper::maybeStartPlaying()
+{
+ // See if we're allowed to autoplay now.
+ if (!isEligible()
+ || !isInViewportIfNeeded()) {
+ return false;
+ }
+
+ // Start playing!
+ prepareToPlay(GesturelessPlaybackStartedByScroll);
+ m_element.playInternal();
+
+ return true;
+}
+
+bool AutoplayExperimentHelper::isEligible() 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_element.isUserGestureRequiredForPlay())
+ return false;
+
+ if (m_mode == ExperimentOff)
+ return false;
+
+ // If nobody has requested playback, either by the autoplay attribute or
+ // a play() call, then do nothing.
+ if (!m_playPending && !m_element.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_element.paused())
+ return false;
+
+ // Note that the viewport test always returns false on desktop, which is
+ // why video-autoplay-experiment.html doesn't check -ifmobile .
+ if ((m_mode & ExperimentIfMobile)
+ && !document().viewportDescription().isLegacyViewportType())
+ return false;
+
+ if (m_mode & ExperimentIfMuted) {
+ // If media is muted, then autoplay when it comes into view.
+ return m_element.fastHasAttribute(mutedAttr) || m_element.muted();
+ }
+
+ // Autoplay when it comes into view (if needed), maybe muted.
+ return true;
+}
+
+void AutoplayExperimentHelper::muteIfNeeded()
+{
+ if (m_mode & ExperimentPlayMuted && !m_element.muted()) {
+ // This will call onMuteChanged(), which we really don't want
+ // to do anything, since we're called when trying to play. If
+ // the element is still marked as eligible, then we'll probably
philipj_slow 2015/08/13 10:15:40 setMuted returns early if there's no change, so I
liberato (no reviews please) 2015/09/01 06:54:20 infinite recursion: true, though it's not obvious
+ // recurse indefinitely.
+ ASSERT(!isEligible());
+ m_element.setMuted(true);
+ }
+}
+
+void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
+{
+ m_element.recordAutoplayMetric(metric);
+
+ // This also causes !isEligible, so that we don't alow autoplay more than
+ // once. Be sure to do this before muteIfNeeded().
+ m_element.removeUserGestureRequirement();
+
+ clearEventListenerIfNeeded();
+ muteIfNeeded();
+
+ // 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_element.setInitialPlayWithoutUserGestures(true);
+
+ // Do not actually start playback here.
+}
+
+Document& AutoplayExperimentHelper::document() const
+{
+ return m_element.document();
+}
+
+}

Powered by Google App Engine
This is Rietveld 408576698