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

Side by Side 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 unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "config.h" 5 #include "config.h"
6 #include "core/html/AutoplayExperimentHelper.h" 6 #include "core/html/AutoplayExperimentHelper.h"
7 7
8 #include "core/dom/Document.h" 8 #include "core/dom/Document.h"
9 #include "core/frame/FrameView.h"
9 #include "core/frame/Settings.h" 10 #include "core/frame/Settings.h"
10 #include "core/html/HTMLMediaElement.h" 11 #include "core/html/HTMLMediaElement.h"
11 #include "core/layout/LayoutBox.h" 12 #include "core/layout/LayoutBox.h"
12 #include "core/layout/LayoutObject.h" 13 #include "core/layout/LayoutObject.h"
13 #include "core/layout/LayoutVideo.h" 14 #include "core/layout/LayoutVideo.h"
14 #include "core/layout/LayoutView.h" 15 #include "core/layout/LayoutView.h"
15 #include "core/page/Page.h" 16 #include "core/page/Page.h"
16 #include "platform/Logging.h" 17 #include "platform/Logging.h"
17 #include "platform/UserGestureIndicator.h" 18 #include "platform/UserGestureIndicator.h"
18 #include "platform/geometry/IntRect.h" 19 #include "platform/geometry/IntRect.h"
19 20
20 namespace blink { 21 namespace blink {
21 22
22 using namespace HTMLNames; 23 using namespace HTMLNames;
23 24
25 // Seconds to wait after a video has stopped moving before playing it.
26 static const double kViewportTimerPollDelay = 0.5;
27
24 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) 28 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
25 : m_element(element) 29 : m_element(element)
26 , m_mode(AutoplayExperimentConfig::Mode::Off) 30 , m_mode(Mode::ExperimentOff)
27 , m_playPending(false) 31 , m_playPending(false)
32 , m_registeredWithLayoutObject(false)
33 , m_wasInViewport(false)
34 , m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity())
35 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
28 { 36 {
29 if (document().settings()) { 37 if (document().settings()) {
30 m_mode = AutoplayExperimentConfig::fromString(document().settings()->aut oplayExperimentMode()); 38 m_mode = fromString(document().settings()->autoplayExperimentMode());
31 39
32 if (m_mode != AutoplayExperimentConfig::Mode::Off) { 40 if (m_mode != Mode::ExperimentOff) {
33 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", 41 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d",
34 m_mode); 42 m_mode);
35 } 43 }
36 } 44 }
37 } 45 }
38 46
39 AutoplayExperimentHelper::~AutoplayExperimentHelper() 47 AutoplayExperimentHelper::~AutoplayExperimentHelper()
40 { 48 {
49 unregisterForPositionUpdatesIfNeeded();
41 } 50 }
42 51
43 void AutoplayExperimentHelper::becameReadyToPlay() 52 void AutoplayExperimentHelper::becameReadyToPlay()
44 { 53 {
45 // Assuming that we're eligible to override the user gesture requirement, 54 // Assuming that we're eligible to override the user gesture requirement,
46 // then play. 55 // either play if we meet the visibility checks, or install a listener
56 // to wait for them to pass.
47 if (isEligible()) { 57 if (isEligible()) {
48 prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); 58 if (meetsVisibilityRequirements())
59 prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
60 else
61 registerForPositionUpdatesIfNeeded();
49 } 62 }
50 } 63 }
51 64
52 void AutoplayExperimentHelper::playMethodCalled() 65 void AutoplayExperimentHelper::playMethodCalled()
53 { 66 {
54 // Set the pending state, even if the play isn't going to be pending. 67 // Set the pending state, even if the play isn't going to be pending.
55 // Eligibility can change if, for example, the mute status changes. 68 // Eligibility can change if, for example, the mute status changes.
56 // Having this set is okay. 69 // Having this set is okay.
57 m_playPending = true; 70 m_playPending = true;
58 71
59 if (!UserGestureIndicator::processingUserGesture()) { 72 if (!UserGestureIndicator::processingUserGesture()) {
60 73
61 if (isEligible()) { 74 if (isEligible()) {
62 // Remember that userGestureRequiredForPlay is required for 75 // Remember that userGestureRequiredForPlay is required for
63 // us to be eligible for the experiment. 76 // us to be eligible for the experiment.
64 // We are able to override the gesture requirement now, so 77 // If we are able to override the gesture requirement now, then
65 // do so. 78 // do so. Otherwise, install an event listener if we need one.
66 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately); 79 if (meetsVisibilityRequirements()) {
80 // Override the gesture and play.
81 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately) ;
82 } else {
83 // Wait for viewport visibility.
84 registerForPositionUpdatesIfNeeded();
85 }
67 } 86 }
68 87
88 } else if (m_element.isUserGestureRequiredForPlay()) {
89 unregisterForPositionUpdatesIfNeeded();
69 } 90 }
70 } 91 }
71 92
72 void AutoplayExperimentHelper::pauseMethodCalled() 93 void AutoplayExperimentHelper::pauseMethodCalled()
73 { 94 {
74 // Don't try to autoplay, if we would have. 95 // Don't try to autoplay, if we would have.
75 m_playPending = false; 96 m_playPending = false;
97 unregisterForPositionUpdatesIfNeeded();
76 } 98 }
77 99
78 void AutoplayExperimentHelper::mutedChanged() 100 void AutoplayExperimentHelper::mutedChanged()
79 { 101 {
80 // In other words, start playing if we just needed 'mute' to autoplay. 102 // If we are no longer eligible for the autoplay experiment, then also
103 // quit listening for events. If we are eligible, and if we should be
104 // playing, then start playing. In other words, start playing if
105 // we just needed 'mute' to autoplay.
106 if (!isEligible()) {
107 unregisterForPositionUpdatesIfNeeded();
108 } else {
109 // Try to play. If we can't, then install a listener.
110 if (!maybeStartPlaying())
111 registerForPositionUpdatesIfNeeded();
112 }
113 }
114
115 void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded()
116 {
117 // If we don't require that the player is in the viewport, then we don't
118 // need the listener.
119 if (!enabled(IfViewport)) {
120 if (!enabled(IfPageVisible))
121 return;
122 }
123
124 if (LayoutObject* layoutObject = m_element.layoutObject()) {
125 LayoutMedia* layoutMedia = toLayoutMedia(layoutObject);
126 layoutMedia->setRequestPositionUpdates(true);
127 }
128
129 // Set this unconditionally, in case we have no layout object yet.
130 m_registeredWithLayoutObject = true;
131 }
132
133 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded()
134 {
135 if (m_registeredWithLayoutObject) {
136 if (LayoutObject* obj = m_element.layoutObject()) {
137 LayoutMedia* layoutMedia = toLayoutMedia(obj);
138 layoutMedia->setRequestPositionUpdates(false);
139 }
140 }
141
142 // Clear this unconditionally so that we don't re-register if we didn't
143 // have a LayoutObject now, but get one later.
144 m_registeredWithLayoutObject = false;
145 }
146
147 void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect)
148 {
149 // Something, maybe position, has changed. If applicable, start a
150 // timer to look for the end of a scroll operation.
151 // Don't do much work here.
152 // Also note that we are called quite often, including when the
153 // page becomes visible. That's why we don't bother to register
154 // for page visibility changes explicitly.
155
156 m_lastVisibleRect = visibleRect;
157
158 if (!m_element.layoutObject())
159 return;
160
161 IntRect currentLocation = m_element.layoutObject()->absoluteBoundingBoxRect( );
162 bool inViewport = meetsVisibilityRequirements();
163
164 if (m_lastLocation != currentLocation) {
165 m_lastLocationUpdateTime = monotonicallyIncreasingTime();
166 m_lastLocation = currentLocation;
167 }
168
169 if (inViewport && !m_wasInViewport) {
170 // Only reset the timer when we transition from not visible to
171 // visible, because resetting the timer isn't cheap.
172 m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE);
173 }
174 m_wasInViewport = inViewport;
175 }
176
177 void AutoplayExperimentHelper::updatePositionNotificationRegistration()
178 {
179 if (m_registeredWithLayoutObject) {
180 LayoutMedia* layoutMedia = toLayoutMedia(m_element.layoutObject());
181 layoutMedia->setRequestPositionUpdates(true);
182 }
183 }
184
185 void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting()
186 {
187 FrameView* view = document().view();
188 if (view)
189 positionChanged(view->rootFrameToContents(view->computeVisibleArea()));
190
191 // Make sure that the last update appears to be sufficiently far in the
192 // past to appear that scrolling has stopped by now in viewportTimerFired.
193 m_lastLocationUpdateTime = monotonicallyIncreasingTime() - kViewportTimerPol lDelay - 1;
194 viewportTimerFired(nullptr);
195 }
196
197 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*)
198 {
199 double now = monotonicallyIncreasingTime();
200 double delta = now - m_lastLocationUpdateTime;
201 if (delta < kViewportTimerPollDelay) {
202 // If we are not visible, then skip the timer. It will be started
203 // again if we become visible again.
204 if (m_wasInViewport)
205 m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta, BLINK_ FROM_HERE);
206
207 return;
208 }
209
210 // Sufficient time has passed since the last scroll that we'll
211 // treat it as the end of scroll. Autoplay if we should.
81 maybeStartPlaying(); 212 maybeStartPlaying();
82 } 213 }
83 214
215 bool AutoplayExperimentHelper::meetsVisibilityRequirements() const
216 {
217 if (enabled(IfPageVisible)
218 && m_element.document().pageVisibilityState() != PageVisibilityStateVisi ble)
219 return false;
220
221 if (!enabled(IfViewport))
222 return true;
223
224 if (m_lastVisibleRect.isEmpty())
225 return false;
226
227 LayoutObject* layoutObject = m_element.layoutObject();
228 if (!layoutObject)
229 return false;
230
231 IntRect currentLocation = layoutObject->absoluteBoundingBoxRect();
232
233 // If element completely fills the screen, then truncate it to exactly
234 // match the screen. Any element that is wider just has to cover.
235 if (currentLocation.x() <= m_lastVisibleRect.x()
236 && currentLocation.x() + currentLocation.width() >= m_lastVisibleRect.x( ) + m_lastVisibleRect.width()) {
237 currentLocation.setX(m_lastVisibleRect.x());
238 currentLocation.setWidth(m_lastVisibleRect.width());
239 }
240
241 if (currentLocation.y() <= m_lastVisibleRect.y()
242 && currentLocation.y() + currentLocation.height() >= m_lastVisibleRect.y () + m_lastVisibleRect.height()) {
243 currentLocation.setY(m_lastVisibleRect.y());
244 currentLocation.setHeight(m_lastVisibleRect.height());
245 }
246
247 return m_lastVisibleRect.contains(currentLocation);
248 }
249
84 bool AutoplayExperimentHelper::maybeStartPlaying() 250 bool AutoplayExperimentHelper::maybeStartPlaying()
85 { 251 {
86 // See if we're allowed to autoplay now. 252 // See if we're allowed to autoplay now.
87 if (!isEligible()) { 253 if (!isEligible() || !meetsVisibilityRequirements()) {
88 return false; 254 return false;
89 } 255 }
90 256
91 // Start playing! 257 // Start playing!
92 prepareToPlay(m_element.shouldAutoplay() 258 prepareToPlay(m_element.shouldAutoplay()
93 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll 259 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll
94 : GesturelessPlaybackStartedByPlayMethodAfterScroll); 260 : GesturelessPlaybackStartedByPlayMethodAfterScroll);
95 m_element.playInternal(); 261 m_element.playInternal();
96 262
97 return true; 263 return true;
98 } 264 }
99 265
100 bool AutoplayExperimentHelper::isEligible() const 266 bool AutoplayExperimentHelper::isEligible() const
101 { 267 {
268 if (m_mode == Mode::ExperimentOff)
269 return false;
270
102 // If no user gesture is required, then the experiment doesn't apply. 271 // If no user gesture is required, then the experiment doesn't apply.
103 // This is what prevents us from starting playback more than once. 272 // This is what prevents us from starting playback more than once.
104 // Since this flag is never set to true once it's cleared, it will block 273 // Since this flag is never set to true once it's cleared, it will block
105 // the autoplay experiment forever. 274 // the autoplay experiment forever.
106 if (!m_element.isUserGestureRequiredForPlay()) 275 if (!m_element.isUserGestureRequiredForPlay())
107 return false; 276 return false;
108 277
109 if (m_mode == AutoplayExperimentConfig::Mode::Off)
110 return false;
111
112 // Make sure that this is an element of the right type. 278 // Make sure that this is an element of the right type.
113 if (!enabled(AutoplayExperimentConfig::Mode::ForVideo) 279 if (!enabled(ForVideo)
114 && isHTMLVideoElement(m_element)) 280 && isHTMLVideoElement(m_element))
115 return false; 281 return false;
116 282
117 if (!enabled(AutoplayExperimentConfig::Mode::ForAudio) 283 if (!enabled(ForAudio)
118 && isHTMLAudioElement(m_element)) 284 && isHTMLAudioElement(m_element))
119 return false; 285 return false;
120 286
121 // If nobody has requested playback, either by the autoplay attribute or 287 // If nobody has requested playback, either by the autoplay attribute or
122 // a play() call, then do nothing. 288 // a play() call, then do nothing.
289
123 if (!m_playPending && !m_element.shouldAutoplay()) 290 if (!m_playPending && !m_element.shouldAutoplay())
124 return false; 291 return false;
125 292
126 // If the video is already playing, then do nothing. Note that there
127 // is not a path where a user gesture is required but the video is
128 // playing. However, we check for completeness.
129 if (!m_element.paused())
130 return false;
131
132 // Note that the viewport test always returns false on desktop, which is 293 // Note that the viewport test always returns false on desktop, which is
133 // why video-autoplay-experiment.html doesn't check -ifmobile . 294 // why video-autoplay-experiment.html doesn't check -ifmobile .
134 if (enabled(AutoplayExperimentConfig::Mode::IfMobile) 295 if (enabled(IfMobile)
135 && !document().viewportDescription().isLegacyViewportType()) 296 && !document().viewportDescription().isLegacyViewportType())
136 return false; 297 return false;
137 298
138 // If media is muted, then autoplay when it comes into view. 299 // If we require muted media and this is muted, then it is eligible.
139 if (enabled(AutoplayExperimentConfig::Mode::IfMuted)) 300 if (enabled(IfMuted))
140 return m_element.muted(); 301 return m_element.muted();
141 302
142 // Autoplay when it comes into view (if needed), maybe muted. 303 // Element is eligible for gesture override, maybe muted.
143 return true; 304 return true;
144 } 305 }
145 306
146 void AutoplayExperimentHelper::muteIfNeeded() 307 void AutoplayExperimentHelper::muteIfNeeded()
147 { 308 {
148 if (enabled(AutoplayExperimentConfig::Mode::PlayMuted)) { 309 if (enabled(PlayMuted)) {
149 ASSERT(!isEligible()); 310 ASSERT(!isEligible());
150 // If we are actually changing the muted state, then this will call 311 // If we are actually changing the muted state, then this will call
151 // mutedChanged(). If isEligible(), then mutedChanged() will try 312 // mutedChanged(). If isEligible(), then mutedChanged() will try
152 // to start playback, which we should not do here. 313 // to start playback, which we should not do here.
153 m_element.setMuted(true); 314 m_element.setMuted(true);
154 } 315 }
155 } 316 }
156 317
157 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) 318 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
158 { 319 {
159 m_element.recordAutoplayMetric(metric); 320 m_element.recordAutoplayMetric(metric);
160 321
161 // This also causes !isEligible, so that we don't allow autoplay more than 322 // This also causes !isEligible, so that we don't allow autoplay more than
162 // once. Be sure to do this before muteIfNeeded(). 323 // once. Be sure to do this before muteIfNeeded().
163 m_element.removeUserGestureRequirement(); 324 m_element.removeUserGestureRequirement();
164 325
326 unregisterForPositionUpdatesIfNeeded();
165 muteIfNeeded(); 327 muteIfNeeded();
166 328
167 // Record that this autoplayed without a user gesture. This is normally 329 // Record that this autoplayed without a user gesture. This is normally
168 // set when we discover an autoplay attribute, but we include all cases 330 // set when we discover an autoplay attribute, but we include all cases
169 // where playback started without a user gesture, e.g., play(). 331 // where playback started without a user gesture, e.g., play().
170 m_element.setInitialPlayWithoutUserGestures(true); 332 m_element.setInitialPlayWithoutUserGestures(true);
171 333
172 // Do not actually start playback here. 334 // Do not actually start playback here.
173 } 335 }
174 336
175 Document& AutoplayExperimentHelper::document() const 337 Document& AutoplayExperimentHelper::document() const
176 { 338 {
177 return m_element.document(); 339 return m_element.document();
178 } 340 }
179 341
342 AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(const String & mode)
343 {
344 Mode value = ExperimentOff;
345 if (mode.contains("-forvideo"))
346 value |= ForVideo;
347 if (mode.contains("-foraudio"))
348 value |= ForAudio;
349 if (mode.contains("-ifpagevisible"))
350 value |= IfPageVisible;
351 if (mode.contains("-ifviewport"))
352 value |= IfViewport;
353 if (mode.contains("-ifmuted"))
354 value |= IfMuted;
355 if (mode.contains("-ifmobile"))
356 value |= IfMobile;
357 if (mode.contains("-playmuted"))
358 value |= PlayMuted;
359
360 return value;
180 } 361 }
362
363 }
OLDNEW
« 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