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

Side by Side Diff: third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp

Issue 1329853004: Include viewport visibility checks for autoplay experiment. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@autoplay_step1
Patch Set: Rebased onto merged blink repo. Created 5 years, 2 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
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/Settings.h" 9 #include "core/frame/Settings.h"
10 #include "core/html/HTMLMediaElement.h" 10 #include "core/html/HTMLMediaElement.h"
11 #include "core/layout/LayoutBox.h" 11 #include "core/layout/LayoutBox.h"
12 #include "core/layout/LayoutObject.h" 12 #include "core/layout/LayoutObject.h"
13 #include "core/layout/LayoutVideo.h" 13 #include "core/layout/LayoutVideo.h"
14 #include "core/layout/LayoutView.h" 14 #include "core/layout/LayoutView.h"
15 #include "core/page/Page.h" 15 #include "core/page/Page.h"
16 #include "platform/Logging.h" 16 #include "platform/Logging.h"
17 #include "platform/UserGestureIndicator.h" 17 #include "platform/UserGestureIndicator.h"
18 #include "platform/geometry/IntRect.h" 18 #include "platform/geometry/IntRect.h"
19 19
20 namespace blink { 20 namespace blink {
21 21
22 using namespace HTMLNames; 22 using namespace HTMLNames;
23 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
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(AutoplayExperimentConfig::Mode::Off)
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 = AutoplayExperimentConfig::fromString(document().settings()->aut oplayExperimentMode());
31 39
32 if (m_mode != AutoplayExperimentConfig::Mode::Off) { 40 if (m_mode != AutoplayExperimentConfig::Mode::Off) {
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(AutoplayExperimentConfig::Mode::IfViewport)
120 && !enabled(AutoplayExperimentConfig::Mode::IfPageVisible))
121 return;
122
123 LayoutObject* layoutObject = m_element.layoutObject();
124 if (layoutObject && layoutObject->isMedia()) {
125 LayoutMedia* layoutMedia = static_cast<LayoutMedia*>(layoutObject);
126 layoutMedia->setRequestPositionUpdates(true);
127 m_registeredWithLayoutObject = true;
128 }
129 }
130
131 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded()
132 {
133 if (m_registeredWithLayoutObject) {
134 LayoutObject* obj = m_element.layoutObject();
135 if (obj && obj->isMedia()) {
136 LayoutMedia* layoutMedia = static_cast<LayoutMedia*>(obj);
137 layoutMedia->setRequestPositionUpdates(false);
138 m_registeredWithLayoutObject = false;
139 }
140 }
141 }
142
143 void AutoplayExperimentHelper::positionChanged()
144 {
145 // Something, maybe position, has changed. If applicable, start a
146 // timer to look for the end of a scroll operation.
147 // Don't do much work here.
148 // Also note that we are called quite often, including when the
149 // page becomes visible. That's why we don't bother to register
150 // for page visibility changes explicitly.
151
152 LocationState curLocation(m_element);
153 const bool inViewport = meetsVisibilityRequirements(curLocation);
154
155 // If the location has changed, then record the time of the last change.
156 if (m_lastLocation != curLocation) {
157 m_lastLocationUpdateTime = monotonicallyIncreasingTime();
158 m_lastLocation = curLocation;
159 }
160
161 // If we have transitioned to be visible, then start the timer.
162 if (inViewport && !m_wasInViewport) {
163 // We have transitioned from not visible to visible. Reset the timer
164 // to check if we should start autoplay. We do this to avoid
165 // resetting the timer too often in this callback.
166 m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE);
167 }
168 m_wasInViewport = inViewport;
169 }
170
171 void AutoplayExperimentHelper::triggerAutoplayViewportCheck()
172 {
173 // This method is for testing.
174
175 // If we're registered to receive updates on position, pretend that we did
176 // receive one.
177 if (m_registeredWithLayoutObject) {
178 positionChanged();
179 }
180
181 // Make sure that the last update appears to be sufficiently far in the
182 // past to appear that scrolling has stopped by now in viewportTimerFired.
183 m_lastLocationUpdateTime = monotonicallyIncreasingTime() - viewportTimerPoll Delay - 1;
184 viewportTimerFired(nullptr);
185 }
186
187 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*)
188 {
189 double now = monotonicallyIncreasingTime();
190 double delta = now - m_lastLocationUpdateTime;
191 if (delta < viewportTimerPollDelay) {
192 // If we are not visible, then skip the timer. It will be started
193 // again if we become visible again.
194 if (m_wasInViewport)
195 m_viewportTimer.startOneShot(viewportTimerPollDelay - delta, FROM_HE RE);
196
197 return;
198 }
199
200 // Sufficient time has passed since the last scroll that we'll
201 // treat it as the end of scroll. Autoplay if we should.
81 maybeStartPlaying(); 202 maybeStartPlaying();
82 } 203 }
83 204
205 bool AutoplayExperimentHelper::meetsVisibilityRequirements(const LocationState& location) const
206 {
207 if (enabled(AutoplayExperimentConfig::Mode::IfPageVisible)
208 && !location.isPageVisible())
209 return false;
210
211 return !enabled(AutoplayExperimentConfig::Mode::IfViewport)
212 || location.isElementVisible();
213 }
214
215 bool AutoplayExperimentHelper::meetsVisibilityRequirements() const
216 {
217 // We could check for eligibility here, but we skip it. Some of our
218 // callers need to do it separately, and we don't want to check more
219 // than we need to.
220
221 // If visibility isn't required, then it's visible enough.
222 if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)
223 && !enabled(AutoplayExperimentConfig::Mode::IfPageVisible))
224 return true;
225
226 LocationState location(m_element);
227 return meetsVisibilityRequirements(location);
228 }
229
84 bool AutoplayExperimentHelper::maybeStartPlaying() 230 bool AutoplayExperimentHelper::maybeStartPlaying()
85 { 231 {
86 // See if we're allowed to autoplay now. 232 // See if we're allowed to autoplay now.
87 if (!isEligible()) { 233 if (!isEligible() || !meetsVisibilityRequirements()) {
88 return false; 234 return false;
89 } 235 }
90 236
91 // Start playing! 237 // Start playing!
92 prepareToPlay(m_element.shouldAutoplay() 238 prepareToPlay(m_element.shouldAutoplay()
93 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll 239 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll
94 : GesturelessPlaybackStartedByPlayMethodAfterScroll); 240 : GesturelessPlaybackStartedByPlayMethodAfterScroll);
95 m_element.playInternal(); 241 m_element.playInternal();
96 242
97 return true; 243 return true;
(...skipping 18 matching lines...) Expand all
116 262
117 if (!enabled(AutoplayExperimentConfig::Mode::ForAudio) 263 if (!enabled(AutoplayExperimentConfig::Mode::ForAudio)
118 && isHTMLAudioElement(m_element)) 264 && isHTMLAudioElement(m_element))
119 return false; 265 return false;
120 266
121 // If nobody has requested playback, either by the autoplay attribute or 267 // If nobody has requested playback, either by the autoplay attribute or
122 // a play() call, then do nothing. 268 // a play() call, then do nothing.
123 if (!m_playPending && !m_element.shouldAutoplay()) 269 if (!m_playPending && !m_element.shouldAutoplay())
124 return false; 270 return false;
125 271
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 272 // Note that the viewport test always returns false on desktop, which is
133 // why video-autoplay-experiment.html doesn't check -ifmobile . 273 // why video-autoplay-experiment.html doesn't check -ifmobile .
134 if (enabled(AutoplayExperimentConfig::Mode::IfMobile) 274 if (enabled(AutoplayExperimentConfig::Mode::IfMobile)
135 && !document().viewportDescription().isLegacyViewportType()) 275 && !document().viewportDescription().isLegacyViewportType())
136 return false; 276 return false;
137 277
138 // If media is muted, then autoplay when it comes into view. 278 // If we require muted media and this is muted, then it is eligible.
139 if (enabled(AutoplayExperimentConfig::Mode::IfMuted)) 279 if (enabled(AutoplayExperimentConfig::Mode::IfMuted))
140 return m_element.muted(); 280 return m_element.muted();
141 281
142 // Autoplay when it comes into view (if needed), maybe muted. 282 // Element is eligible for gesture override, maybe muted.
143 return true; 283 return true;
144 } 284 }
145 285
146 void AutoplayExperimentHelper::muteIfNeeded() 286 void AutoplayExperimentHelper::muteIfNeeded()
147 { 287 {
148 if (enabled(AutoplayExperimentConfig::Mode::PlayMuted)) { 288 if (enabled(AutoplayExperimentConfig::Mode::PlayMuted)) {
149 ASSERT(!isEligible()); 289 ASSERT(!isEligible());
150 // If we are actually changing the muted state, then this will call 290 // If we are actually changing the muted state, then this will call
151 // mutedChanged(). If isEligible(), then mutedChanged() will try 291 // mutedChanged(). If isEligible(), then mutedChanged() will try
152 // to start playback, which we should not do here. 292 // to start playback, which we should not do here.
153 m_element.setMuted(true); 293 m_element.setMuted(true);
154 } 294 }
155 } 295 }
156 296
157 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) 297 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
158 { 298 {
159 m_element.recordAutoplayMetric(metric); 299 m_element.recordAutoplayMetric(metric);
160 300
161 // This also causes !isEligible, so that we don't allow autoplay more than 301 // This also causes !isEligible, so that we don't allow autoplay more than
162 // once. Be sure to do this before muteIfNeeded(). 302 // once. Be sure to do this before muteIfNeeded().
163 m_element.removeUserGestureRequirement(); 303 m_element.removeUserGestureRequirement();
164 304
305 unregisterForPositionUpdatesIfNeeded();
165 muteIfNeeded(); 306 muteIfNeeded();
166 307
167 // Record that this autoplayed without a user gesture. This is normally 308 // Record that this autoplayed without a user gesture. This is normally
168 // set when we discover an autoplay attribute, but we include all cases 309 // set when we discover an autoplay attribute, but we include all cases
169 // where playback started without a user gesture, e.g., play(). 310 // where playback started without a user gesture, e.g., play().
170 m_element.setInitialPlayWithoutUserGestures(true); 311 m_element.setInitialPlayWithoutUserGestures(true);
171 312
172 // Do not actually start playback here. 313 // Do not actually start playback here.
173 } 314 }
174 315
175 Document& AutoplayExperimentHelper::document() const 316 Document& AutoplayExperimentHelper::document() const
176 { 317 {
177 return m_element.document(); 318 return m_element.document();
178 } 319 }
179 320
321 AutoplayExperimentHelper::LocationState::LocationState(Element& element)
322 {
323 // Default to empty rectangles. The screen is offset so that contains()
324 // returns false.
325 m_element = IntRect();
326 m_screen = IntRect(-1, -1, 0, 0);
327 ASSERT(!m_screen.contains(m_element));
328 m_visibilityState = PageVisibilityStateHidden;
329
330 const LocalDOMWindow* domWindow = element.document().domWindow();
331 if (!domWindow)
332 return;
333
334 // Get the page visibility.
335 Frame* frame = domWindow->frame();
336 if (!frame)
337 return;
338
339 Page* page = frame->page();
340 if (!page)
341 return;
342
343 m_visibilityState = page->visibilityState();
344
345 if (!element.layoutObject())
346 return;
347
348 const LayoutBox* elementBox = element.layoutObject()->enclosingBox();
349 if (!elementBox)
350 return;
351
352 float zoom = elementBox->style()->effectiveZoom();
353 m_element = IntRect(elementBox->offsetLeft().toInt()
354 , elementBox->offsetTop().toInt()
355 , elementBox->clientWidth().toInt()
356 , elementBox->clientHeight().toInt());
357
358 m_screen = IntRect(domWindow->scrollX()*zoom
359 , domWindow->scrollY()*zoom
360 , domWindow->innerWidth()*zoom
361 , domWindow->innerHeight()*zoom);
180 } 362 }
363
364 bool AutoplayExperimentHelper::LocationState::isElementVisible() const
365 {
366 // Check if we're in the viewport. If the element is bigger than the
367 // viewport, then be happy if it covers it.
368
369 IntRect element = m_element;
370 // If element completely fills the screen, then truncate it to exactly
371 // match the screen. Any element that is wider just has to cover.
372 if (element.x() <= m_screen.x()
373 && element.x() + element.width() >= m_screen.x() + m_screen.width()) {
374 element.setX(m_screen.x());
375 element.setWidth(m_screen.width());
376 }
377
378 if (element.y() <= m_screen.y()
379 && element.y() + element.height() >= m_screen.y() + m_screen.height()) {
380 element.setY(m_screen.y());
381 element.setHeight(m_screen.height());
382 }
383
384 return m_screen.contains(element);
385 }
386
387 bool AutoplayExperimentHelper::LocationState::isPageVisible() const
388 {
389 return m_visibilityState == PageVisibilityStateVisible;
390 }
391
392 bool AutoplayExperimentHelper::LocationState::operator==(const LocationState& th em) const
393 {
394 // As a special case, if either is missing position information, then
395 // they are not equal.
396 return !m_element.isEmpty() && !them.element().isEmpty()
397 && !m_screen.isEmpty() && !them.screen().isEmpty()
398 && m_visibilityState == them.visibilityState()
399 && m_screen == them.screen()
400 && m_element == them.element();
401 }
402
403 bool AutoplayExperimentHelper::LocationState::operator!=(const LocationState& th em) const
404 {
405 return !(*this == them);
406 }
407
408 }
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