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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2015 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "core/html/AutoplayExperimentHelper.h"
33
34 #include "core/dom/Document.h"
35 #include "core/frame/Settings.h"
36 #include "core/html/HTMLMediaElement.h"
37 #include "platform/UserGestureIndicator.h"
38 #include "platform/geometry/FloatRect.h"
39
40 namespace blink {
41
42 using namespace HTMLNames;
43
44 // How long do we wait after a scroll event before deciding that no more
45 // scroll events are going to arrive?
46 static const double viewportTimerPollDelay = 0.5;
47
48 // Event listener that just informs the AutoplayExperimentHelper that a
49 // scroll has happened.
50 class AutoplayExperimentHelper::ScrollListener : public EventListener {
51 public:
52 ScrollListener(AutoplayExperimentHelper* helper) : EventListener(CPPEven tListenerType), m_helper(helper) { }
53 virtual bool operator==(const EventListener& them)
54 {
55 return &them == this;
56 }
57
58 void handleEvent(ExecutionContext*, Event*) override
59 {
60 if (m_helper)
61 m_helper->notifyScrolled();
62 }
63
64 private:
65 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.
66 };
67
68
69 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
70 : m_element(element)
71 , m_mode(ExperimentOff)
72 , m_playPending(false)
73 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
74 {
75 if (document().settings()) {
76 const String& autoplayMode = document().settings()->autoplayExperimentMo de();
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
77 if (autoplayMode.contains("enabled")) {
78 // Autoplay with no gesture requirement.
79 m_mode |= ExperimentEnabled;
80 }
81 if (autoplayMode.contains("-ifviewport")) {
82 // Override gesture requirement only if the player is within the
83 // current viewport.
84 m_mode |= ExperimentIfViewport;
85 }
86 if (autoplayMode.contains("-ifmuted")) {
87 // Override gesture requirement only if the media is muted or has
88 // no audio track.
89 m_mode |= ExperimentIfMuted;
90 }
91 if (autoplayMode.contains("-ifmobile")) {
92 // Override gesture requirement only if the page is optimized
93 // for mobile.
94 m_mode |= ExperimentIfMobile;
95 }
96 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
97 m_mode |= ExperimentPlayMuted;
98 }
99
100 if (m_mode != ExperimentOff) {
101 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to '%s' (% d)",
102 autoplayMode.ascii().data(), m_mode);
103 }
104 }
105 }
106
107 AutoplayExperimentHelper::~AutoplayExperimentHelper()
108 {
109 clearEventListenerIfNeeded();
110 }
111
112 void AutoplayExperimentHelper::onReadyToPlay()
113 {
114 // Assuming that we're eligible to override the user gesture requirement,
115 // either play if we meet the visibility checks, or install a listener
116 // to wait for them to pass.
117 if (isEligible()) {
118 if (isInViewportIfNeeded())
119 prepareToPlay(GesturelessPlaybackStartedByLoad);
120 else
121 installEventListenerIfNeeded();
122 }
123 }
124
125 void AutoplayExperimentHelper::onPlayMethodCalled()
126 {
127 // Set the pending state, even if the play isn't going to be pending.
128 // Eligibility can change if, for example, the mute status changes.
129 // Having this set is okay.
130 m_playPending = true;
131
132 if (!UserGestureIndicator::processingUserGesture()) {
133
134 if (isEligible()) {
135 // Remember that userGestureRequiredForPlay is required for
136 // us to be eligible for the experiment.
137 // If we are able to override the gesture requirement now, then
138 // do so. Otherwise, install an event listener if we need one.
139 if (isInViewportIfNeeded()) {
140 // Override the gesture and play.
141 prepareToPlay(GesturelessPlaybackStartedByPlayMethod);
142 } else {
143 // Wait for viewport visibility.
144 installEventListenerIfNeeded();
145 }
146 }
147
148 } else if (m_element.isUserGestureRequiredForPlay()) {
149 clearEventListenerIfNeeded();
150 }
151 }
152
153 void AutoplayExperimentHelper::onPauseMethodCalled()
154 {
155 // Don't try to autoplay, if we would have.
156 m_playPending = false;
157 clearEventListenerIfNeeded();
158 }
159
160 void AutoplayExperimentHelper::onMuteChanged()
161 {
162 // If we are no longer eligible for the autoplay experiment, then also
163 // quit listening for events. If we are eligible, and if we should be
164 // playing, then start playing. In other words, start playing if
165 // we just needed 'mute' to autoplay.
166 if (!isEligible()) {
167 clearEventListenerIfNeeded();
168 } else {
169 // Try to play. If we can't, then install a listener.
170 if (!maybeStartPlaying())
171 installEventListenerIfNeeded();
172 }
173 }
174
175 void AutoplayExperimentHelper::installEventListenerIfNeeded()
176 {
177 // If we don't require that the player is in the viewport, then we don't
178 // need the listener.
179 if (!(m_mode & ExperimentIfViewport))
180 return;
181
182 if (document().domWindow() && !m_scrollListener) {
183 m_scrollListener = adoptRef(new ScrollListener(this));
184 document().domWindow()->addEventListener("scroll", m_scrollListener, fal se);
185 }
186 }
187
188 void AutoplayExperimentHelper::clearEventListenerIfNeeded()
189 {
190 if (m_scrollListener) {
191 LocalDOMWindow* domWindow = document().domWindow();
192 if (domWindow) {
193 domWindow->removeEventListener("scroll", m_scrollListener, false);
194 }
195 // Either way, clear our ref.
196 m_scrollListener.clear();
197 }
198 }
199
200 void AutoplayExperimentHelper::notifyScrolled()
201 {
202 // Reset the timer to indicate that scrolling has happened
203 // recently, and might still be ongoing.
204 m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE);
205 }
206
207 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*)
208 {
209 // Sufficient time has passed since the last scroll that we'll
210 // treat it as the end of scroll. Autoplay if we should.
211 maybeStartPlaying();
212 }
213
214 bool AutoplayExperimentHelper::isInViewportIfNeeded()
215 {
216 // We could check for eligibility here, but we skip it. Some of our
217 // callers need to do it separately, and we don't want to check more
218 // than we need to.
219 // Also remember that page visibility is assumed for clank.
220
221 // If viewport visibility isn't required, then it's visible enough.
222 if (!(m_mode & ExperimentIfViewport))
223 return true;
224
225 // Check if we're in the viewport.
226 const LocalDOMWindow* domWindow = document().domWindow();
227 if (!domWindow)
228 return false;
229
230 FloatRect us(m_element.offsetLeft(), m_element.offsetTop(), m_element.client Width(), m_element.clientHeight());
231 FloatRect screen(domWindow->scrollX(), domWindow->scrollY(), domWindow->inne rWidth(), domWindow->innerHeight());
232
233 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
234 }
235
236 bool AutoplayExperimentHelper::maybeStartPlaying()
237 {
238 // See if we're allowed to autoplay now.
239 if (!isEligible()
240 || !isInViewportIfNeeded()) {
241 return false;
242 }
243
244 // Start playing!
245 prepareToPlay(GesturelessPlaybackStartedByScroll);
246 m_element.playInternal();
247
248 return true;
249 }
250
251 bool AutoplayExperimentHelper::isEligible() const
252 {
253 // If no user gesture is required, then the experiment doesn't apply.
254 // This is what prevents us from starting playback more than once.
255 // Since this flag is never set to true once it's cleared, it will block
256 // the autoplay experiment forever.
257 if (!m_element.isUserGestureRequiredForPlay())
258 return false;
259
260 if (m_mode == ExperimentOff)
261 return false;
262
263 // If nobody has requested playback, either by the autoplay attribute or
264 // a play() call, then do nothing.
265 if (!m_playPending && !m_element.autoplay())
266 return false;
267
268 // If the video is already playing, then do nothing. Note that there
269 // is not a path where a user gesture is required but the video is
270 // playing. However, we check for completeness.
271 if (!m_element.paused())
272 return false;
273
274 // Note that the viewport test always returns false on desktop, which is
275 // why video-autoplay-experiment.html doesn't check -ifmobile .
276 if ((m_mode & ExperimentIfMobile)
277 && !document().viewportDescription().isLegacyViewportType())
278 return false;
279
280 if (m_mode & ExperimentIfMuted) {
281 // If media is muted, then autoplay when it comes into view.
282 return m_element.fastHasAttribute(mutedAttr) || m_element.muted();
283 }
284
285 // Autoplay when it comes into view (if needed), maybe muted.
286 return true;
287 }
288
289 void AutoplayExperimentHelper::muteIfNeeded()
290 {
291 if (m_mode & ExperimentPlayMuted && !m_element.muted()) {
292 // This will call onMuteChanged(), which we really don't want
293 // to do anything, since we're called when trying to play. If
294 // 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
295 // recurse indefinitely.
296 ASSERT(!isEligible());
297 m_element.setMuted(true);
298 }
299 }
300
301 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
302 {
303 m_element.recordAutoplayMetric(metric);
304
305 // This also causes !isEligible, so that we don't alow autoplay more than
306 // once. Be sure to do this before muteIfNeeded().
307 m_element.removeUserGestureRequirement();
308
309 clearEventListenerIfNeeded();
310 muteIfNeeded();
311
312 // Record that this autoplayed without a user gesture. This is normally
313 // set when we discover an autoplay attribute, but we include all cases
314 // where playback started without a user gesture, e.g., play().
315 m_element.setInitialPlayWithoutUserGestures(true);
316
317 // Do not actually start playback here.
318 }
319
320 Document& AutoplayExperimentHelper::document() const
321 {
322 return m_element.document();
323 }
324
325 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698