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

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: linker errors on win/mac... Created 5 years, 3 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 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "config.h"
6 #include "core/html/AutoplayExperimentHelper.h"
7
8 #include "core/dom/Document.h"
9 #include "core/frame/Settings.h"
10 #include "core/html/HTMLMediaElement.h"
11 #include "core/layout/LayoutBox.h"
12 #include "core/layout/LayoutObject.h"
13 #include "core/layout/LayoutVideo.h"
14 #include "core/layout/LayoutView.h"
15 #include "core/page/Page.h"
16 #include "platform/Logging.h"
17 #include "platform/UserGestureIndicator.h"
18 #include "platform/geometry/IntRect.h"
19
20 namespace blink {
21
22 using namespace HTMLNames;
23
24 // How long do we wait after a scroll event before deciding that no more
25 // scroll events are going to arrive?
26 static const double viewportTimerPollDelay = 0.5;
27
28 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
29 : m_element(element)
30 , m_mode(AutoplayExperimentConfig::Mode::Off)
31 , m_playPending(false)
32 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
33 , m_registeredWithView(false)
34 {
35 if (document().settings()) {
36 m_mode = AutoplayExperimentConfig::fromString(document().settings()->aut oplayExperimentMode());
37
38 if (m_mode != AutoplayExperimentConfig::Mode::Off) {
39 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d",
40 m_mode);
41 }
42 }
43 }
44
45 AutoplayExperimentHelper::~AutoplayExperimentHelper()
46 {
47 clearEventListenerIfNeeded();
48 }
49
50 void AutoplayExperimentHelper::becameReadyToPlay()
51 {
52 // Assuming that we're eligible to override the user gesture requirement,
53 // either play if we meet the visibility checks, or install a listener
54 // to wait for them to pass.
55 if (isEligible()) {
56 if (isInViewportIfNeeded())
57 prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
58 else
59 installEventListenerIfNeeded();
60 }
61 }
62
63 void AutoplayExperimentHelper::playMethodCalled()
64 {
65 // Set the pending state, even if the play isn't going to be pending.
66 // Eligibility can change if, for example, the mute status changes.
67 // Having this set is okay.
68 m_playPending = true;
69
70 if (!UserGestureIndicator::processingUserGesture()) {
71
72 if (isEligible()) {
73 // Remember that userGestureRequiredForPlay is required for
74 // us to be eligible for the experiment.
75 // If we are able to override the gesture requirement now, then
76 // do so. Otherwise, install an event listener if we need one.
77 if (isInViewportIfNeeded()) {
78 // Override the gesture and play.
79 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately) ;
80 } else {
81 // Wait for viewport visibility.
82 installEventListenerIfNeeded();
83 }
84 }
85
86 } else if (m_element.isUserGestureRequiredForPlay()) {
87 clearEventListenerIfNeeded();
88 }
89 }
90
91 void AutoplayExperimentHelper::pauseMethodCalled()
92 {
93 // Don't try to autoplay, if we would have.
94 m_playPending = false;
95 clearEventListenerIfNeeded();
96 }
97
98 void AutoplayExperimentHelper::mutedChanged()
99 {
100 // If we are no longer eligible for the autoplay experiment, then also
101 // quit listening for events. If we are eligible, and if we should be
102 // playing, then start playing. In other words, start playing if
103 // we just needed 'mute' to autoplay.
104 if (!isEligible()) {
105 clearEventListenerIfNeeded();
106 } else {
107 // Try to play. If we can't, then install a listener.
108 if (!maybeStartPlaying())
109 installEventListenerIfNeeded();
110 }
111 }
112
113 void AutoplayExperimentHelper::installEventListenerIfNeeded()
114 {
115 // If we don't require that the player is in the viewport, then we don't
116 // need the listener.
117 if (!enabled(AutoplayExperimentConfig::Mode::IfViewport))
118 return;
119
120 LayoutObject* layoutObject = m_element.layoutObject();
121 if (layoutObject && layoutObject->isVideo()) {
122 LayoutVideo* layoutVideo = (LayoutVideo*)layoutObject;
philipj_slow 2015/09/02 09:24:11 Use static_cast
liberato (no reviews please) 2015/09/04 06:49:45 Done.
123 layoutVideo->setRequestPositionUpdates(true);
124 // TODO(liberato): do we really need to keep track of this? it's
philipj_slow 2015/09/02 09:24:11 Is this TODO to be fixed before landing? Who knows
liberato (no reviews please) 2015/09/04 06:49:46 yes, it will. i wanted to put it out for CL with
philipj_slow 2015/09/04 08:42:29 OK, I see this will be done in a separate CL.
125 // only to make clearEventListener() faster.
126 m_registeredWithView = true;
127 }
128 }
129
130 void AutoplayExperimentHelper::clearEventListenerIfNeeded()
131 {
132 if (m_registeredWithView) {
133 LayoutObject* obj = m_element.layoutObject();
134 if (obj && obj->isVideo()) {
135 LayoutVideo* video = (LayoutVideo*)obj;
philipj_slow 2015/09/02 09:24:11 static_cast
liberato (no reviews please) 2015/09/04 06:49:45 thanks, forgot what year it is.
136 video->setRequestPositionUpdates(false);
137 m_registeredWithView = false;
138 }
139 }
140 }
141
142 void AutoplayExperimentHelper::positionChanged()
143 {
144 // Reset the timer to indicate that scrolling has happened
145 // recently, and might still be ongoing.
146 // Also note that we are called quite often, including when the
147 // page becomes visible. That's why we don't bother to register
148 // for page visibility changes explicitly.
149
150 // Since we're called very often, even if our visibility hasn't changed,
151 // make sure that we only reset the timer if something has moved.
152 // Otherwise, we will reset the timer every time a video frame plays
153 // anywhere, or the mouse moves, etc.
154 // We may want to lower the frequency of this via another timer, so that
155 // we do no work here.
156 LocationState curLocation(m_element);
157 if (curLocation != m_lastLocation) {
158 m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE);
159 m_lastLocation = curLocation;
160 }
161 }
162
163 void AutoplayExperimentHelper::triggerAutoplayViewportCheck()
164 {
165 viewportTimerFired(0);
philipj_slow 2015/09/02 09:24:11 nullptr
liberato (no reviews please) 2015/09/04 06:49:46 Done.
166 }
167
168 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*)
169 {
170 // Sufficient time has passed since the last scroll that we'll
171 // treat it as the end of scroll. Autoplay if we should.
philipj_slow 2015/09/02 09:24:12 I didn't follow the discussion around this very cl
liberato (no reviews please) 2015/09/04 06:49:45 no, unfortunately.
philipj_slow 2015/09/04 08:42:29 Acknowledged.
172 maybeStartPlaying();
173 }
174
175 bool AutoplayExperimentHelper::isInViewportIfNeeded()
176 {
177 // We could check for eligibility here, but we skip it. Some of our
178 // callers need to do it separately, and we don't want to check more
179 // than we need to.
180 // Also remember that page visibility is assumed for clank.
181
182 // If viewport visibility isn't required, then it's visible enough.
183 if (!enabled(AutoplayExperimentConfig::Mode::IfViewport))
184 return true;
185
186 return LocationState(m_element).isInViewport();
187 }
188
189 bool AutoplayExperimentHelper::maybeStartPlaying()
190 {
191 // See if we're allowed to autoplay now.
192 if (!isEligible()
philipj_slow 2015/09/02 09:24:11 A bit much line breaking here, it shouldn't be ver
liberato (no reviews please) 2015/09/04 06:49:46 Done.
193 || !isInViewportIfNeeded()) {
194 return false;
195 }
196
197 // Start playing!
198 prepareToPlay(m_element.autoplay()
philipj_slow 2015/09/02 09:24:11 autoplay() checks the content attribute, but there
liberato (no reviews please) 2015/09/04 06:49:45 good point. i'll add this for clarity, but i thin
philipj_slow 2015/09/04 08:42:29 It should be possible to clear the autoplaying fla
199 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll
200 : GesturelessPlaybackStartedByPlayMethodAfterScroll);
201 m_element.playInternal();
202
203 return true;
204 }
205
206 bool AutoplayExperimentHelper::isEligible() const
207 {
208 // If no user gesture is required, then the experiment doesn't apply.
209 // This is what prevents us from starting playback more than once.
210 // Since this flag is never set to true once it's cleared, it will block
211 // the autoplay experiment forever.
212 if (!m_element.isUserGestureRequiredForPlay())
213 return false;
214
215 if (m_mode == AutoplayExperimentConfig::Mode::Off)
216 return false;
217
218 // If nobody has requested playback, either by the autoplay attribute or
219 // a play() call, then do nothing.
220 if (!m_playPending && !m_element.autoplay())
philipj_slow 2015/09/02 09:24:11 shouldAutoplay() here too. To write a test for the
liberato (no reviews please) 2015/09/04 06:49:46 Done, including test.
221 return false;
222
223 // If the video is already playing, then do nothing. Note that there
224 // is not a path where a user gesture is required but the video is
225 // playing. However, we check for completeness.
226 if (!m_element.paused())
227 return false;
228
229 // Note that the viewport test always returns false on desktop, which is
230 // why video-autoplay-experiment.html doesn't check -ifmobile .
231 if (enabled(AutoplayExperimentConfig::Mode::IfMobile)
232 && !document().viewportDescription().isLegacyViewportType())
233 return false;
234
235 if (enabled(AutoplayExperimentConfig::Mode::IfMuted)) {
236 // If media is muted, then autoplay when it comes into view.
philipj_slow 2015/09/02 09:24:11 Move this outside the if and remove {} for consist
liberato (no reviews please) 2015/09/04 06:49:45 Done.
237 return m_element.fastHasAttribute(mutedAttr) || m_element.muted();
238 }
239
240 // Autoplay when it comes into view (if needed), maybe muted.
241 return true;
242 }
243
244 void AutoplayExperimentHelper::muteIfNeeded()
245 {
246 if (enabled(AutoplayExperimentConfig::Mode::PlayMuted)) {
247 ASSERT(!isEligible());
248 // If we are actually changing the muted state, then this will call
249 // mutedChanged(). If isEligible(), then mutedChanged() will try
250 // to start playback, which we should not do here.
251 m_element.setMuted(true);
252 }
253 }
254
255 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
256 {
257 m_element.recordAutoplayMetric(metric);
258
259 // This also causes !isEligible, so that we don't alow autoplay more than
philipj_slow 2015/09/02 09:24:11 s/alow/allow/
liberato (no reviews please) 2015/09/04 06:49:45 Done.
260 // once. Be sure to do this before muteIfNeeded().
261 m_element.removeUserGestureRequirement();
262
263 clearEventListenerIfNeeded();
264 muteIfNeeded();
265
266 // Record that this autoplayed without a user gesture. This is normally
267 // set when we discover an autoplay attribute, but we include all cases
268 // where playback started without a user gesture, e.g., play().
269 m_element.setInitialPlayWithoutUserGestures(true);
270
271 // Do not actually start playback here.
272 }
273
274 Document& AutoplayExperimentHelper::document() const
275 {
276 return m_element.document();
277 }
278
279 AutoplayExperimentHelper::LocationState::LocationState(Element& element)
280 : m_valid(false)
281 {
282 const LocalDOMWindow* domWindow = element.document().domWindow();
283 if (!domWindow)
284 return;
285
286 // Get the page visibility.
287 Frame* frame = domWindow->frame();
288 if (!frame)
289 return;
290
291 Page* page = frame->page();
292 if (!page)
293 return;
294
295 if (!element.layoutObject())
296 return;
297
298 const LayoutBox* elementBox = element.layoutObject()->enclosingBox();
299 if (!elementBox)
300 return;
301
302 float zoom = elementBox->style()->effectiveZoom();
303 IntRect us(elementBox->offsetLeft().toInt()
304 , elementBox->offsetTop().toInt()
305 , elementBox->clientWidth().toInt()
306 , elementBox->clientHeight().toInt());
307 IntRect screen(domWindow->scrollX()*zoom, domWindow->scrollY()*zoom, domWind ow->innerWidth()*zoom, domWindow->innerHeight()*zoom);
308
309 m_visibilityState = page->visibilityState();
310 m_element = us;
311 m_screen = screen;
312 m_valid = true;
313 }
314
315 bool AutoplayExperimentHelper::LocationState::isInViewport()
316 {
317 // Check if we're in the viewport.
318 return m_valid
319 && m_visibilityState == PageVisibilityStateVisible
320 && m_screen.contains(m_element);
321 }
322
323 bool AutoplayExperimentHelper::LocationState::operator==(const LocationState& th em) const
324 {
325 // If either state is not valid, then they are not equal.
326 return m_valid && them.valid()
327 && m_visibilityState == them.visibilityState()
328 && m_screen == them.screen()
329 && m_element == them.element();
330 }
331
332 bool AutoplayExperimentHelper::LocationState::operator!=(const LocationState& th em) const
333 {
334 return !((*this) == them);
335 }
336
337 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698