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

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

Issue 1470153004: Autoplay experiment metric fixes and additions. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebased. Created 4 years, 11 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 "core/html/AutoplayExperimentHelper.h" 5 #include "core/html/AutoplayExperimentHelper.h"
6 6
7 #include "core/dom/Document.h" 7 #include "core/dom/Document.h"
8 #include "core/frame/FrameView.h"
9 #include "core/frame/Settings.h" 8 #include "core/frame/Settings.h"
10 #include "core/html/HTMLMediaElement.h" 9 #include "core/html/HTMLMediaElement.h"
11 #include "core/layout/LayoutBox.h" 10 #include "core/layout/LayoutBox.h"
12 #include "core/layout/LayoutObject.h" 11 #include "core/layout/LayoutObject.h"
13 #include "core/layout/LayoutVideo.h" 12 #include "core/layout/LayoutVideo.h"
14 #include "core/layout/LayoutView.h" 13 #include "core/layout/LayoutView.h"
15 #include "core/page/Page.h" 14 #include "core/page/Page.h"
16 #include "platform/Logging.h" 15 #include "platform/Logging.h"
17 #include "platform/UserGestureIndicator.h" 16 #include "platform/UserGestureIndicator.h"
18 #include "platform/geometry/IntRect.h" 17 #include "platform/geometry/IntRect.h"
19 18
20 namespace blink { 19 namespace blink {
21 20
22 using namespace HTMLNames; 21 using namespace HTMLNames;
23 22
24 // Seconds to wait after a video has stopped moving before playing it. 23 // Seconds to wait after a video has stopped moving before playing it.
25 static const double kViewportTimerPollDelay = 0.5; 24 static const double kViewportTimerPollDelay = 0.5;
26 25
27 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) 26 AutoplayExperimentHelper::AutoplayExperimentHelper(Client& client)
28 : m_element(&element) 27 : m_client(client)
29 , m_mode(Mode::ExperimentOff) 28 , m_mode(Mode::ExperimentOff)
30 , m_playPending(false) 29 , m_playPending(false)
31 , m_registeredWithLayoutObject(false) 30 , m_registeredWithLayoutObject(false)
32 , m_wasInViewport(false) 31 , m_wasInViewport(false)
32 , m_autoplayMediaCounted(false)
33 , m_playbackStartedBefore(false)
34 , m_initialPlayWithoutUserGesture(false)
35 , m_recordedElement(false)
33 , m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity()) 36 , m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity())
34 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired) 37 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
38 , m_autoplayDeferredMetric(GesturelessPlaybackUnknownReason)
35 { 39 {
36 if (document().settings()) { 40 m_mode = fromString(this->client().autoplayExperimentMode());
37 m_mode = fromString(document().settings()->autoplayExperimentMode());
38 41
39 if (m_mode != Mode::ExperimentOff) { 42 if (m_mode != Mode::ExperimentOff) {
40 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", 43 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d",
41 m_mode); 44 m_mode);
42 }
43 } 45 }
44 } 46 }
45 47
46 AutoplayExperimentHelper::~AutoplayExperimentHelper() 48 AutoplayExperimentHelper::~AutoplayExperimentHelper()
47 { 49 {
48 unregisterForPositionUpdatesIfNeeded(); 50 unregisterForPositionUpdatesIfNeeded();
49 } 51 }
50 52
51 void AutoplayExperimentHelper::becameReadyToPlay() 53 void AutoplayExperimentHelper::becameReadyToPlay()
52 { 54 {
53 // Assuming that we're eligible to override the user gesture requirement, 55 // Assuming that we're eligible to override the user gesture requirement,
54 // either play if we meet the visibility checks, or install a listener 56 // either play if we meet the visibility checks, or install a listener
55 // to wait for them to pass. 57 // to wait for them to pass. We do not actually start playback; our
58 // caller must do that.
59 autoplayMediaEncountered();
60
56 if (isEligible()) { 61 if (isEligible()) {
57 if (meetsVisibilityRequirements()) 62 if (meetsVisibilityRequirements())
58 prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); 63 prepareToAutoplay(GesturelessPlaybackStartedByAutoplayFlagImmediatel y);
59 else 64 else
60 registerForPositionUpdatesIfNeeded(); 65 registerForPositionUpdatesIfNeeded();
61 } 66 }
62 } 67 }
63 68
64 void AutoplayExperimentHelper::playMethodCalled() 69 void AutoplayExperimentHelper::playMethodCalled()
65 { 70 {
66 // Set the pending state, even if the play isn't going to be pending. 71 // Set the pending state, even if the play isn't going to be pending.
67 // Eligibility can change if, for example, the mute status changes. 72 // Eligibility can change if, for example, the mute status changes.
68 // Having this set is okay. 73 // Having this set is okay.
69 m_playPending = true; 74 m_playPending = true;
70 75
71 if (!UserGestureIndicator::processingUserGesture()) { 76 if (!UserGestureIndicator::processingUserGesture()) {
77 autoplayMediaEncountered();
72 78
73 if (isEligible()) { 79 if (isEligible()) {
74 // Remember that userGestureRequiredForPlay is required for 80 // Remember that userGestureRequiredForPlay is required for
75 // us to be eligible for the experiment. 81 // us to be eligible for the experiment.
76 // If we are able to override the gesture requirement now, then 82 // If we are able to override the gesture requirement now, then
77 // do so. Otherwise, install an event listener if we need one. 83 // do so. Otherwise, install an event listener if we need one.
78 if (meetsVisibilityRequirements()) { 84 if (meetsVisibilityRequirements()) {
79 // Override the gesture and play. 85 // Override the gesture and assume that play() will succeed.
80 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately) ; 86 prepareToAutoplay(GesturelessPlaybackStartedByPlayMethodImmediat ely);
81 } else { 87 } else {
82 // Wait for viewport visibility. 88 // Wait for viewport visibility.
83 registerForPositionUpdatesIfNeeded(); 89 registerForPositionUpdatesIfNeeded();
84 } 90 }
85 } 91 }
92 } else if (isUserGestureRequiredForPlay()) {
93 if (m_autoplayMediaCounted)
94 recordAutoplayMetric(AutoplayManualStart);
95 // Don't let future gestureless playbacks affect metrics.
philipj_slow 2016/01/12 15:12:16 This doesn't seem true, AutoplayManualStart could
liberato (no reviews please) 2016/01/29 08:25:24 good catch. i think this was guarded before, and
96 m_autoplayMediaCounted = true;
97 m_playbackStartedBefore = true;
philipj_slow 2016/01/12 15:12:17 Shouldn't m_playbackStartedBefore be set only in p
liberato (no reviews please) 2016/01/29 08:25:24 i didn't just in case it doesn't get that far. i'
philipj_slow 2016/02/02 08:08:15 Acknowledged.
86 98
87 } else if (element().isUserGestureRequiredForPlay()) {
88 unregisterForPositionUpdatesIfNeeded(); 99 unregisterForPositionUpdatesIfNeeded();
89 } 100 }
90 } 101 }
91 102
92 void AutoplayExperimentHelper::pauseMethodCalled() 103 void AutoplayExperimentHelper::pauseMethodCalled()
93 { 104 {
94 // Don't try to autoplay, if we would have. 105 // Don't try to autoplay, if we would have.
95 m_playPending = false; 106 m_playPending = false;
96 unregisterForPositionUpdatesIfNeeded(); 107 unregisterForPositionUpdatesIfNeeded();
108
109 if (!client().paused())
110 recordMetricsBeforePause();
111 }
112
113 void AutoplayExperimentHelper::loadMethodCalled()
114 {
115 if (!client().paused())
116 recordMetricsBeforePause();
philipj_slow 2016/01/12 15:12:17 I wouldn't say that load() amounts to pausing. Som
liberato (no reviews please) 2016/01/29 08:25:24 true, but not recording anything doesn't seem righ
philipj_slow 2016/02/02 08:08:15 If it were common it would taint the metric for pa
117
118 if (UserGestureIndicator::processingUserGesture() && isUserGestureRequiredFo rPlay()) {
119 recordAutoplayMetric(AutoplayEnabledThroughLoad);
philipj_slow 2016/01/12 15:12:17 Should AutoplayEnabledThroughLoad simply be remove
liberato (no reviews please) 2016/01/29 08:25:24 the former is recorded when the override happens,
philipj_slow 2016/02/02 08:08:14 Oh, different function call there :)
120 removeUserGestureRequirement(GesturelessPlaybackEnabledByLoad);
121 // While usergesture-initiated load()s technically count as autoplayed,
philipj_slow 2016/01/12 15:12:17 Why don't these plays feel like autoplay? And isn'
liberato (no reviews please) 2016/01/29 08:25:24 now that we're recording "GesturelessPlaybackEnabl
122 // they don't feel like such to the users and hence we don't want to
123 // count them for the purposes of metrics.
124 m_autoplayMediaCounted = true;
125 m_playbackStartedBefore = true;
126 }
97 } 127 }
98 128
99 void AutoplayExperimentHelper::mutedChanged() 129 void AutoplayExperimentHelper::mutedChanged()
100 { 130 {
101 // If we are no longer eligible for the autoplay experiment, then also 131 // If we are no longer eligible for the autoplay experiment, then also
102 // quit listening for events. If we are eligible, and if we should be 132 // quit listening for events. If we are eligible, and if we should be
103 // playing, then start playing. In other words, start playing if 133 // playing, then start playing. In other words, start playing if
104 // we just needed 'mute' to autoplay. 134 // we just needed 'mute' to autoplay.
135
136 // Make sure that autoplay was actually deferred. If, for example, the
137 // autoplay attribute is set after the media is ready to play, then it
138 // would normally have no effect. We don't want to start playing.
139 if (!m_autoplayMediaCounted)
140 return;
141
105 if (!isEligible()) { 142 if (!isEligible()) {
106 unregisterForPositionUpdatesIfNeeded(); 143 unregisterForPositionUpdatesIfNeeded();
107 } else { 144 } else {
108 // Try to play. If we can't, then install a listener. 145 // Try to play. If we can't, then install a listener.
109 if (!maybeStartPlaying()) 146 if (!maybeStartPlaying())
110 registerForPositionUpdatesIfNeeded(); 147 registerForPositionUpdatesIfNeeded();
111 } 148 }
112 } 149 }
113 150
114 void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded() 151 void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded()
115 { 152 {
116 // If we don't require that the player is in the viewport, then we don't 153 // If we don't require that the player is in the viewport, then we don't
117 // need the listener. 154 // need the listener.
118 if (!enabled(IfViewport)) { 155 if (!enabled(IfViewport)) {
119 if (!enabled(IfPageVisible)) 156 if (!enabled(IfPageVisible))
120 return; 157 return;
121 } 158 }
122 159
123 if (LayoutObject* layoutObject = element().layoutObject()) { 160 client().setRequestPositionUpdates(true);
124 LayoutMedia* layoutMedia = toLayoutMedia(layoutObject);
125 layoutMedia->setRequestPositionUpdates(true);
126 }
127 161
128 // Set this unconditionally, in case we have no layout object yet. 162 // Set this unconditionally, in case we have no layout object yet.
129 m_registeredWithLayoutObject = true; 163 m_registeredWithLayoutObject = true;
130 } 164 }
131 165
132 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded() 166 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded()
133 { 167 {
134 if (m_registeredWithLayoutObject) { 168 if (m_registeredWithLayoutObject)
135 if (LayoutObject* obj = element().layoutObject()) { 169 client().setRequestPositionUpdates(false);
136 LayoutMedia* layoutMedia = toLayoutMedia(obj);
137 layoutMedia->setRequestPositionUpdates(false);
138 }
139 }
140 170
141 // Clear this unconditionally so that we don't re-register if we didn't 171 // Clear this unconditionally so that we don't re-register if we didn't
142 // have a LayoutObject now, but get one later. 172 // have a LayoutObject now, but get one later.
143 m_registeredWithLayoutObject = false; 173 m_registeredWithLayoutObject = false;
144 } 174 }
145 175
146 void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect) 176 void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect)
147 { 177 {
148 // Something, maybe position, has changed. If applicable, start a 178 // Something, maybe position, has changed. If applicable, start a
149 // timer to look for the end of a scroll operation. 179 // timer to look for the end of a scroll operation.
150 // Don't do much work here. 180 // Don't do much work here.
151 // Also note that we are called quite often, including when the 181 // Also note that we are called quite often, including when the
152 // page becomes visible. That's why we don't bother to register 182 // page becomes visible. That's why we don't bother to register
153 // for page visibility changes explicitly. 183 // for page visibility changes explicitly.
184 if (visibleRect.isEmpty())
185 return;
154 186
155 m_lastVisibleRect = visibleRect; 187 m_lastVisibleRect = visibleRect;
156 188
157 if (!element().layoutObject()) 189 IntRect currentLocation = client().absoluteBoundingBoxRect();
190 if (currentLocation.isEmpty())
158 return; 191 return;
159 192
160 IntRect currentLocation = element().layoutObject()->absoluteBoundingBoxRect( );
161 bool inViewport = meetsVisibilityRequirements(); 193 bool inViewport = meetsVisibilityRequirements();
162 194
163 if (m_lastLocation != currentLocation) { 195 if (m_lastLocation != currentLocation) {
164 m_lastLocationUpdateTime = monotonicallyIncreasingTime(); 196 m_lastLocationUpdateTime = monotonicallyIncreasingTime();
165 m_lastLocation = currentLocation; 197 m_lastLocation = currentLocation;
166 } 198 }
167 199
168 if (inViewport && !m_wasInViewport) { 200 if (inViewport && !m_wasInViewport) {
169 // Only reset the timer when we transition from not visible to 201 // Only reset the timer when we transition from not visible to
170 // visible, because resetting the timer isn't cheap. 202 // visible, because resetting the timer isn't cheap.
171 m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE); 203 m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE);
172 } 204 }
173 m_wasInViewport = inViewport; 205 m_wasInViewport = inViewport;
174 } 206 }
175 207
176 void AutoplayExperimentHelper::updatePositionNotificationRegistration() 208 void AutoplayExperimentHelper::updatePositionNotificationRegistration()
177 { 209 {
178 if (m_registeredWithLayoutObject) { 210 if (m_registeredWithLayoutObject)
179 LayoutMedia* layoutMedia = toLayoutMedia(element().layoutObject()); 211 client().setRequestPositionUpdates(true);
180 layoutMedia->setRequestPositionUpdates(true);
181 }
182 } 212 }
183 213
184 void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting() 214 void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting()
185 { 215 {
186 FrameView* view = document().view();
187 if (view)
188 positionChanged(view->rootFrameToContents(view->computeVisibleArea()));
189
190 // Make sure that the last update appears to be sufficiently far in the 216 // Make sure that the last update appears to be sufficiently far in the
191 // past to appear that scrolling has stopped by now in viewportTimerFired. 217 // past to appear that scrolling has stopped by now in viewportTimerFired.
192 m_lastLocationUpdateTime = monotonicallyIncreasingTime() - kViewportTimerPol lDelay - 1; 218 m_lastLocationUpdateTime = monotonicallyIncreasingTime() - kViewportTimerPol lDelay - 1;
193 viewportTimerFired(nullptr); 219 viewportTimerFired(nullptr);
194 } 220 }
195 221
196 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*) 222 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*)
197 { 223 {
198 double now = monotonicallyIncreasingTime(); 224 double now = monotonicallyIncreasingTime();
199 double delta = now - m_lastLocationUpdateTime; 225 double delta = now - m_lastLocationUpdateTime;
200 if (delta < kViewportTimerPollDelay) { 226 if (delta < kViewportTimerPollDelay) {
201 // If we are not visible, then skip the timer. It will be started 227 // If we are not visible, then skip the timer. It will be started
202 // again if we become visible again. 228 // again if we become visible again.
203 if (m_wasInViewport) 229 if (m_wasInViewport)
204 m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta, BLINK_ FROM_HERE); 230 m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta, BLINK_ FROM_HERE);
205 231
206 return; 232 return;
207 } 233 }
208 234
209 // Sufficient time has passed since the last scroll that we'll 235 // Sufficient time has passed since the last scroll that we'll
210 // treat it as the end of scroll. Autoplay if we should. 236 // treat it as the end of scroll. Autoplay if we should.
211 maybeStartPlaying(); 237 maybeStartPlaying();
212 } 238 }
213 239
214 bool AutoplayExperimentHelper::meetsVisibilityRequirements() const 240 bool AutoplayExperimentHelper::meetsVisibilityRequirements() const
215 { 241 {
216 if (enabled(IfPageVisible) 242 if (enabled(IfPageVisible)
217 && element().document().pageVisibilityState() != PageVisibilityStateVisi ble) 243 && client().pageVisibilityState() != PageVisibilityStateVisible)
218 return false; 244 return false;
219 245
220 if (!enabled(IfViewport)) 246 if (!enabled(IfViewport))
221 return true; 247 return true;
222 248
223 if (m_lastVisibleRect.isEmpty()) 249 if (m_lastVisibleRect.isEmpty())
224 return false; 250 return false;
225 251
226 LayoutObject* layoutObject = element().layoutObject(); 252 IntRect currentLocation = client().absoluteBoundingBoxRect();
227 if (!layoutObject) 253 if (currentLocation.isEmpty())
228 return false; 254 return false;
229 255
230 IntRect currentLocation = layoutObject->absoluteBoundingBoxRect();
231
232 // If element completely fills the screen, then truncate it to exactly 256 // If element completely fills the screen, then truncate it to exactly
233 // match the screen. Any element that is wider just has to cover. 257 // match the screen. Any element that is wider just has to cover.
234 if (currentLocation.x() <= m_lastVisibleRect.x() 258 if (currentLocation.x() <= m_lastVisibleRect.x()
235 && currentLocation.x() + currentLocation.width() >= m_lastVisibleRect.x( ) + m_lastVisibleRect.width()) { 259 && currentLocation.x() + currentLocation.width() >= m_lastVisibleRect.x( ) + m_lastVisibleRect.width()) {
236 currentLocation.setX(m_lastVisibleRect.x()); 260 currentLocation.setX(m_lastVisibleRect.x());
237 currentLocation.setWidth(m_lastVisibleRect.width()); 261 currentLocation.setWidth(m_lastVisibleRect.width());
238 } 262 }
239 263
240 if (currentLocation.y() <= m_lastVisibleRect.y() 264 if (currentLocation.y() <= m_lastVisibleRect.y()
241 && currentLocation.y() + currentLocation.height() >= m_lastVisibleRect.y () + m_lastVisibleRect.height()) { 265 && currentLocation.y() + currentLocation.height() >= m_lastVisibleRect.y () + m_lastVisibleRect.height()) {
242 currentLocation.setY(m_lastVisibleRect.y()); 266 currentLocation.setY(m_lastVisibleRect.y());
243 currentLocation.setHeight(m_lastVisibleRect.height()); 267 currentLocation.setHeight(m_lastVisibleRect.height());
244 } 268 }
245 269
246 return m_lastVisibleRect.contains(currentLocation); 270 return m_lastVisibleRect.contains(currentLocation);
247 } 271 }
248 272
249 bool AutoplayExperimentHelper::maybeStartPlaying() 273 bool AutoplayExperimentHelper::maybeStartPlaying()
250 { 274 {
251 // See if we're allowed to autoplay now. 275 // See if we're allowed to autoplay now.
252 if (!isEligible() || !meetsVisibilityRequirements()) { 276 if (!isEligible() || !meetsVisibilityRequirements()) {
253 return false; 277 return false;
254 } 278 }
255 279
256 // Start playing! 280 // Start playing!
257 prepareToPlay(element().shouldAutoplay() 281 prepareToAutoplay(client().shouldAutoplay()
258 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll 282 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll
259 : GesturelessPlaybackStartedByPlayMethodAfterScroll); 283 : GesturelessPlaybackStartedByPlayMethodAfterScroll);
260 element().playInternal(); 284
285 // Record that this played without a user gesture.
286 // This should rarely actually do anything. Usually, playMethodCalled()
287 // and becameReadyToPlay will handle it, but toggling muted state can,
288 // in some cases, also trigger autoplay if the autoplay attribute is set
289 // after the media is ready to play.
290 // TODO(liberato): remove this.
philipj_slow 2016/01/12 15:12:17 Remove it how?
liberato (no reviews please) 2016/01/29 08:25:24 i was planning to change how 'is muted' works, so
philipj_slow 2016/02/02 08:08:14 Welcome back :)
291 autoplayMediaEncountered();
292
293 client().playInternal();
261 294
262 return true; 295 return true;
263 } 296 }
264 297
265 bool AutoplayExperimentHelper::isEligible() const 298 bool AutoplayExperimentHelper::isEligible() const
266 { 299 {
267 if (m_mode == Mode::ExperimentOff) 300 if (m_mode == Mode::ExperimentOff)
268 return false; 301 return false;
269 302
270 // If no user gesture is required, then the experiment doesn't apply. 303 // If no user gesture is required, then the experiment doesn't apply.
271 // This is what prevents us from starting playback more than once. 304 // This is what prevents us from starting playback more than once.
272 // Since this flag is never set to true once it's cleared, it will block 305 // Since this flag is never set to true once it's cleared, it will block
273 // the autoplay experiment forever. 306 // the autoplay experiment forever.
274 if (!element().isUserGestureRequiredForPlay()) 307 if (!isUserGestureRequiredForPlay())
275 return false; 308 return false;
276 309
277 // Make sure that this is an element of the right type. 310 // Make sure that this is an element of the right type.
278 if (!enabled(ForVideo) && isHTMLVideoElement(element())) 311 if (!enabled(ForVideo) && client().isHTMLVideoElement())
279 return false; 312 return false;
280 313
281 if (!enabled(ForAudio) && isHTMLAudioElement(element())) 314 if (!enabled(ForAudio) && client().isHTMLAudioElement())
282 return false; 315 return false;
283 316
284 // If nobody has requested playback, either by the autoplay attribute or 317 // If nobody has requested playback, either by the autoplay attribute or
285 // a play() call, then do nothing. 318 // a play() call, then do nothing.
286 319
287 if (!m_playPending && !element().shouldAutoplay()) 320 if (!m_playPending && !client().shouldAutoplay())
288 return false; 321 return false;
289 322
290 // Note that the viewport test always returns false on desktop, which is 323 // Note that the viewport test always returns false on desktop, which is
291 // why video-autoplay-experiment.html doesn't check -ifmobile . 324 // why video-autoplay-experiment.html doesn't check -ifmobile .
292 if (enabled(IfMobile) 325 if (enabled(IfMobile)
293 && !document().viewportDescription().isLegacyViewportType()) 326 && !client().isLegacyViewportType())
294 return false; 327 return false;
295 328
296 // If we require muted media and this is muted, then it is eligible. 329 // If we require muted media and this is muted, then it is eligible.
297 if (enabled(IfMuted)) 330 if (enabled(IfMuted))
298 return element().muted(); 331 return client().muted();
299 332
300 // Element is eligible for gesture override, maybe muted. 333 // Element is eligible for gesture override, maybe muted.
301 return true; 334 return true;
302 } 335 }
303 336
304 void AutoplayExperimentHelper::muteIfNeeded() 337 void AutoplayExperimentHelper::muteIfNeeded()
305 { 338 {
306 if (enabled(PlayMuted)) { 339 if (enabled(PlayMuted)) {
307 ASSERT(!isEligible()); 340 ASSERT(!isEligible());
308 // If we are actually changing the muted state, then this will call 341 // If we are actually changing the muted state, then this will call
309 // mutedChanged(). If isEligible(), then mutedChanged() will try 342 // mutedChanged(). If isEligible(), then mutedChanged() will try
310 // to start playback, which we should not do here. 343 // to start playback, which we should not do here.
311 element().setMuted(true); 344 client().setMuted(true);
312 } 345 }
313 } 346 }
314 347
315 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) 348 void AutoplayExperimentHelper::removeUserGestureRequirement(AutoplayMetrics metr ic)
316 { 349 {
317 element().recordAutoplayMetric(metric); 350 if (client().isUserGestureRequiredForPlay()) {
351 m_autoplayDeferredMetric = metric;
352 client().removeUserGestureRequirement();
353 }
354 }
318 355
356 void AutoplayExperimentHelper::prepareToAutoplay(AutoplayMetrics metric)
357 {
319 // This also causes !isEligible, so that we don't allow autoplay more than 358 // This also causes !isEligible, so that we don't allow autoplay more than
320 // once. Be sure to do this before muteIfNeeded(). 359 // once. Be sure to do this before muteIfNeeded().
321 element().removeUserGestureRequirement(); 360 // Also note that, at this point, we know that we're goint to start
361 // playback. However, we still don't record the metric here. Instead,
362 // we let autoplayMediaEncountered() do that later.
philipj_slow 2016/01/12 15:12:17 It's actually recorded in playbackStarted(), right
liberato (no reviews please) 2016/01/29 08:25:24 it should say playbackStarted. however, i don't s
philipj_slow 2016/02/02 08:08:14 Acknowledged.
363 removeUserGestureRequirement(metric);
364
365 // Don't bother to call autoplayMediaEncountered, since whoever initiates
366 // playback has do it anyway, in case we don't allow autoplay.
322 367
323 unregisterForPositionUpdatesIfNeeded(); 368 unregisterForPositionUpdatesIfNeeded();
324 muteIfNeeded(); 369 muteIfNeeded();
325 370
326 // Record that this autoplayed without a user gesture. This is normally
327 // set when we discover an autoplay attribute, but we include all cases
328 // where playback started without a user gesture, e.g., play().
329 element().setInitialPlayWithoutUserGestures(true);
330
331 // Do not actually start playback here. 371 // Do not actually start playback here.
332 } 372 }
333 373
334 Document& AutoplayExperimentHelper::document() const 374 AutoplayExperimentHelper::Client& AutoplayExperimentHelper::client() const
335 { 375 {
336 return element().document(); 376 return m_client;
337 }
338
339 HTMLMediaElement& AutoplayExperimentHelper::element() const
340 {
341 ASSERT(m_element);
342 return *m_element;
343 } 377 }
344 378
345 AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(const String & mode) 379 AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(const String & mode)
346 { 380 {
347 Mode value = ExperimentOff; 381 Mode value = ExperimentOff;
348 if (mode.contains("-forvideo")) 382 if (mode.contains("-forvideo"))
349 value |= ForVideo; 383 value |= ForVideo;
350 if (mode.contains("-foraudio")) 384 if (mode.contains("-foraudio"))
351 value |= ForAudio; 385 value |= ForAudio;
352 if (mode.contains("-ifpagevisible")) 386 if (mode.contains("-ifpagevisible"))
353 value |= IfPageVisible; 387 value |= IfPageVisible;
354 if (mode.contains("-ifviewport")) 388 if (mode.contains("-ifviewport"))
355 value |= IfViewport; 389 value |= IfViewport;
356 if (mode.contains("-ifmuted")) 390 if (mode.contains("-ifmuted"))
357 value |= IfMuted; 391 value |= IfMuted;
358 if (mode.contains("-ifmobile")) 392 if (mode.contains("-ifmobile"))
359 value |= IfMobile; 393 value |= IfMobile;
360 if (mode.contains("-playmuted")) 394 if (mode.contains("-playmuted"))
361 value |= PlayMuted; 395 value |= PlayMuted;
362 396
363 return value; 397 return value;
364 } 398 }
365 399
400 void AutoplayExperimentHelper::autoplayMediaEncountered()
401 {
402 if (!m_autoplayMediaCounted) {
philipj_slow 2016/01/12 15:12:17 Maybe rename to m_autoplayMediaEncountered? There
liberato (no reviews please) 2016/01/29 08:25:24 Done.
403 m_autoplayMediaCounted = true;
404 recordAutoplayMetric(AutoplayMediaFound);
405 }
366 } 406 }
407
408 bool AutoplayExperimentHelper::isUserGestureRequiredForPlay() const
409 {
410 return client().isUserGestureRequiredForPlay();
411 }
412
413 void AutoplayExperimentHelper::recordMetricsBeforePause()
414 {
415 ASSERT(!client().paused());
416
417 const bool bailout = isBailout();
418
419 // Record that play was paused. We don't care if it was autoplay,
420 // play(), or the user manually started it.
421 recordAutoplayMetric(AnyPlaybackPaused);
422 if (bailout)
423 recordAutoplayMetric(AnyPlaybackBailout);
424
425 // If this was a gestureless play, then record that separately.
426 // These cover attr and play() gestureless starts.
427 if (m_initialPlayWithoutUserGesture) {
428 m_initialPlayWithoutUserGesture = false;
philipj_slow 2016/01/12 15:12:17 Why is m_initialPlayWithoutUserGesture cleared her
liberato (no reviews please) 2016/01/29 08:25:24 good point. it's really more like "need to record
429
430 recordAutoplayMetric(AutoplayPaused);
431
432 if (bailout)
433 recordAutoplayMetric(AutoplayBailout);
434 }
435 }
436
437 void AutoplayExperimentHelper::playbackStarted()
438 {
439 recordAutoplayMetric(AnyPlaybackStarted);
440
441 if (!m_playbackStartedBefore) {
442 m_playbackStartedBefore = true;
443
444 // If this is a gestureless start, record why it was allowed.
445 if (!UserGestureIndicator::processingUserGesture()) {
446 m_initialPlayWithoutUserGesture = true;
447 recordAutoplayMetric(m_autoplayDeferredMetric);
philipj_slow 2016/01/12 15:12:16 assert something about m_autoplayDeferredMetric?
liberato (no reviews please) 2016/01/29 08:25:24 do you mean for the default (GesturelessPlaybackNo
philipj_slow 2016/02/02 08:08:14 Right, that other platform :)
448 }
449 }
450 }
451
452 void AutoplayExperimentHelper::playbackEnded()
453 {
454 recordAutoplayMetric(AnyPlaybackComplete);
455 if (m_initialPlayWithoutUserGesture) {
456 m_initialPlayWithoutUserGesture = false;
philipj_slow 2016/01/12 15:12:16 And why here? Since it's still true that the initi
liberato (no reviews please) 2016/01/29 08:25:24 it prevents pause/end metrics from being recorded
philipj_slow 2016/02/02 08:08:15 Acknowledged.
457 recordAutoplayMetric(AutoplayComplete);
458 }
459 }
460
461 void AutoplayExperimentHelper::recordAutoplayMetric(AutoplayMetrics metric)
462 {
463 client().recordAutoplayMetric(metric);
464 }
465
466 bool AutoplayExperimentHelper::isBailout() const
467 {
468 // We count the user as having bailed-out on the video if they watched
469 // less than one minute and less than 50% of it.
470 const double playedTime = client().currentTime();
471 const double progress = playedTime / client().duration();
472 return (playedTime < 60) && (progress < 0.5);
473 }
474
475 void AutoplayExperimentHelper::recordSandboxFailure()
476 {
477 // We record autoplayMediaEncountered here because we know
478 // that the autoplay attempt will fail.
479 autoplayMediaEncountered();
480 recordAutoplayMetric(AutoplayDisabledBySandbox);
481 }
482
483 void AutoplayExperimentHelper::loadingStarted()
484 {
485 if (m_recordedElement)
486 return;
487
488 m_recordedElement = true;
489 recordAutoplayMetric(client().isHTMLVideoElement()
490 ? AnyVideoElement
491 : AnyAudioElement);
492 }
493 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698