OLD | NEW |
---|---|
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_autoplayMediaEncountered(false) | |
33 , m_playbackStartedMetricRecorded(false) | |
34 , m_waitingForAutoplayPlaybackEnd(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(GesturelessPlaybackNotOverridden) | |
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 this media tried to autoplay, and we haven't played it yet, then | |
94 // record that the user provided the gesture to start it the first time. | |
95 if (m_autoplayMediaEncountered && !m_playbackStartedMetricRecorded) | |
96 recordAutoplayMetric(AutoplayManualStart); | |
97 // Don't let future gestureless playbacks affect metrics. | |
98 m_autoplayMediaEncountered = true; | |
99 m_playbackStartedMetricRecorded = true; | |
86 | 100 |
87 } else if (element().isUserGestureRequiredForPlay()) { | |
88 unregisterForPositionUpdatesIfNeeded(); | 101 unregisterForPositionUpdatesIfNeeded(); |
89 } | 102 } |
90 } | 103 } |
91 | 104 |
92 void AutoplayExperimentHelper::pauseMethodCalled() | 105 void AutoplayExperimentHelper::pauseMethodCalled() |
93 { | 106 { |
94 // Don't try to autoplay, if we would have. | 107 // Don't try to autoplay, if we would have. |
95 m_playPending = false; | 108 m_playPending = false; |
96 unregisterForPositionUpdatesIfNeeded(); | 109 unregisterForPositionUpdatesIfNeeded(); |
110 | |
111 if (!client().paused()) | |
112 recordMetricsBeforePause(); | |
113 } | |
114 | |
115 void AutoplayExperimentHelper::loadMethodCalled() | |
116 { | |
117 if (!client().paused()) | |
118 recordMetricsBeforePause(); | |
119 | |
120 if (UserGestureIndicator::processingUserGesture() && isUserGestureRequiredFo rPlay()) { | |
121 recordAutoplayMetric(AutoplayEnabledThroughLoad); | |
122 removeUserGestureRequirement(GesturelessPlaybackEnabledByLoad); | |
123 } | |
97 } | 124 } |
98 | 125 |
99 void AutoplayExperimentHelper::mutedChanged() | 126 void AutoplayExperimentHelper::mutedChanged() |
100 { | 127 { |
101 // If we are no longer eligible for the autoplay experiment, then also | 128 // 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 | 129 // quit listening for events. If we are eligible, and if we should be |
103 // playing, then start playing. In other words, start playing if | 130 // playing, then start playing. In other words, start playing if |
104 // we just needed 'mute' to autoplay. | 131 // we just needed 'mute' to autoplay. |
132 | |
133 // Make sure that autoplay was actually deferred. If, for example, the | |
134 // autoplay attribute is set after the media is ready to play, then it | |
135 // would normally have no effect. We don't want to start playing. | |
136 if (!m_autoplayMediaEncountered) | |
137 return; | |
138 | |
105 if (!isEligible()) { | 139 if (!isEligible()) { |
106 unregisterForPositionUpdatesIfNeeded(); | 140 unregisterForPositionUpdatesIfNeeded(); |
107 } else { | 141 } else { |
108 // Try to play. If we can't, then install a listener. | 142 // Try to play. If we can't, then install a listener. |
109 if (!maybeStartPlaying()) | 143 if (!maybeStartPlaying()) |
110 registerForPositionUpdatesIfNeeded(); | 144 registerForPositionUpdatesIfNeeded(); |
111 } | 145 } |
112 } | 146 } |
113 | 147 |
114 void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded() | 148 void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded() |
115 { | 149 { |
116 // If we don't require that the player is in the viewport, then we don't | 150 // If we don't require that the player is in the viewport, then we don't |
117 // need the listener. | 151 // need the listener. |
118 if (!enabled(IfViewport)) { | 152 if (!enabled(IfViewport)) { |
119 if (!enabled(IfPageVisible)) | 153 if (!enabled(IfPageVisible)) |
120 return; | 154 return; |
121 } | 155 } |
122 | 156 |
123 if (LayoutObject* layoutObject = element().layoutObject()) { | 157 client().setRequestPositionUpdates(true); |
124 LayoutMedia* layoutMedia = toLayoutMedia(layoutObject); | |
125 layoutMedia->setRequestPositionUpdates(true); | |
126 } | |
127 | 158 |
128 // Set this unconditionally, in case we have no layout object yet. | 159 // Set this unconditionally, in case we have no layout object yet. |
129 m_registeredWithLayoutObject = true; | 160 m_registeredWithLayoutObject = true; |
130 } | 161 } |
131 | 162 |
132 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded() | 163 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded() |
133 { | 164 { |
134 if (m_registeredWithLayoutObject) { | 165 if (m_registeredWithLayoutObject) |
135 if (LayoutObject* obj = element().layoutObject()) { | 166 client().setRequestPositionUpdates(false); |
136 LayoutMedia* layoutMedia = toLayoutMedia(obj); | |
137 layoutMedia->setRequestPositionUpdates(false); | |
138 } | |
139 } | |
140 | 167 |
141 // Clear this unconditionally so that we don't re-register if we didn't | 168 // Clear this unconditionally so that we don't re-register if we didn't |
142 // have a LayoutObject now, but get one later. | 169 // have a LayoutObject now, but get one later. |
143 m_registeredWithLayoutObject = false; | 170 m_registeredWithLayoutObject = false; |
144 } | 171 } |
145 | 172 |
146 void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect) | 173 void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect) |
147 { | 174 { |
148 // Something, maybe position, has changed. If applicable, start a | 175 // Something, maybe position, has changed. If applicable, start a |
149 // timer to look for the end of a scroll operation. | 176 // timer to look for the end of a scroll operation. |
150 // Don't do much work here. | 177 // Don't do much work here. |
151 // Also note that we are called quite often, including when the | 178 // Also note that we are called quite often, including when the |
152 // page becomes visible. That's why we don't bother to register | 179 // page becomes visible. That's why we don't bother to register |
153 // for page visibility changes explicitly. | 180 // for page visibility changes explicitly. |
181 if (visibleRect.isEmpty()) | |
182 return; | |
154 | 183 |
155 m_lastVisibleRect = visibleRect; | 184 m_lastVisibleRect = visibleRect; |
156 | 185 |
157 if (!element().layoutObject()) | 186 IntRect currentLocation = client().absoluteBoundingBoxRect(); |
187 if (currentLocation.isEmpty()) | |
158 return; | 188 return; |
159 | 189 |
160 IntRect currentLocation = element().layoutObject()->absoluteBoundingBoxRect( ); | |
161 bool inViewport = meetsVisibilityRequirements(); | 190 bool inViewport = meetsVisibilityRequirements(); |
162 | 191 |
163 if (m_lastLocation != currentLocation) { | 192 if (m_lastLocation != currentLocation) { |
164 m_lastLocationUpdateTime = monotonicallyIncreasingTime(); | 193 m_lastLocationUpdateTime = monotonicallyIncreasingTime(); |
165 m_lastLocation = currentLocation; | 194 m_lastLocation = currentLocation; |
166 } | 195 } |
167 | 196 |
168 if (inViewport && !m_wasInViewport) { | 197 if (inViewport && !m_wasInViewport) { |
169 // Only reset the timer when we transition from not visible to | 198 // Only reset the timer when we transition from not visible to |
170 // visible, because resetting the timer isn't cheap. | 199 // visible, because resetting the timer isn't cheap. |
171 m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE); | 200 m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE); |
172 } | 201 } |
173 m_wasInViewport = inViewport; | 202 m_wasInViewport = inViewport; |
174 } | 203 } |
175 | 204 |
176 void AutoplayExperimentHelper::updatePositionNotificationRegistration() | 205 void AutoplayExperimentHelper::updatePositionNotificationRegistration() |
177 { | 206 { |
178 if (m_registeredWithLayoutObject) { | 207 if (m_registeredWithLayoutObject) |
179 LayoutMedia* layoutMedia = toLayoutMedia(element().layoutObject()); | 208 client().setRequestPositionUpdates(true); |
180 layoutMedia->setRequestPositionUpdates(true); | |
181 } | |
182 } | 209 } |
183 | 210 |
184 void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting() | 211 void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting() |
185 { | 212 { |
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 | 213 // 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. | 214 // past to appear that scrolling has stopped by now in viewportTimerFired. |
192 m_lastLocationUpdateTime = monotonicallyIncreasingTime() - kViewportTimerPol lDelay - 1; | 215 m_lastLocationUpdateTime = monotonicallyIncreasingTime() - kViewportTimerPol lDelay - 1; |
193 viewportTimerFired(nullptr); | 216 viewportTimerFired(nullptr); |
194 } | 217 } |
195 | 218 |
196 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*) | 219 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*) |
197 { | 220 { |
198 double now = monotonicallyIncreasingTime(); | 221 double now = monotonicallyIncreasingTime(); |
199 double delta = now - m_lastLocationUpdateTime; | 222 double delta = now - m_lastLocationUpdateTime; |
200 if (delta < kViewportTimerPollDelay) { | 223 if (delta < kViewportTimerPollDelay) { |
201 // If we are not visible, then skip the timer. It will be started | 224 // If we are not visible, then skip the timer. It will be started |
202 // again if we become visible again. | 225 // again if we become visible again. |
203 if (m_wasInViewport) | 226 if (m_wasInViewport) |
204 m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta, BLINK_ FROM_HERE); | 227 m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta, BLINK_ FROM_HERE); |
205 | 228 |
206 return; | 229 return; |
207 } | 230 } |
208 | 231 |
209 // Sufficient time has passed since the last scroll that we'll | 232 // Sufficient time has passed since the last scroll that we'll |
210 // treat it as the end of scroll. Autoplay if we should. | 233 // treat it as the end of scroll. Autoplay if we should. |
211 maybeStartPlaying(); | 234 maybeStartPlaying(); |
212 } | 235 } |
213 | 236 |
214 bool AutoplayExperimentHelper::meetsVisibilityRequirements() const | 237 bool AutoplayExperimentHelper::meetsVisibilityRequirements() const |
215 { | 238 { |
216 if (enabled(IfPageVisible) | 239 if (enabled(IfPageVisible) |
217 && element().document().pageVisibilityState() != PageVisibilityStateVisi ble) | 240 && client().pageVisibilityState() != PageVisibilityStateVisible) |
218 return false; | 241 return false; |
219 | 242 |
220 if (!enabled(IfViewport)) | 243 if (!enabled(IfViewport)) |
221 return true; | 244 return true; |
222 | 245 |
223 if (m_lastVisibleRect.isEmpty()) | 246 if (m_lastVisibleRect.isEmpty()) |
224 return false; | 247 return false; |
225 | 248 |
226 LayoutObject* layoutObject = element().layoutObject(); | 249 IntRect currentLocation = client().absoluteBoundingBoxRect(); |
227 if (!layoutObject) | 250 if (currentLocation.isEmpty()) |
228 return false; | 251 return false; |
229 | 252 |
230 IntRect currentLocation = layoutObject->absoluteBoundingBoxRect(); | |
231 | |
232 // If element completely fills the screen, then truncate it to exactly | 253 // If element completely fills the screen, then truncate it to exactly |
233 // match the screen. Any element that is wider just has to cover. | 254 // match the screen. Any element that is wider just has to cover. |
234 if (currentLocation.x() <= m_lastVisibleRect.x() | 255 if (currentLocation.x() <= m_lastVisibleRect.x() |
235 && currentLocation.x() + currentLocation.width() >= m_lastVisibleRect.x( ) + m_lastVisibleRect.width()) { | 256 && currentLocation.x() + currentLocation.width() >= m_lastVisibleRect.x( ) + m_lastVisibleRect.width()) { |
236 currentLocation.setX(m_lastVisibleRect.x()); | 257 currentLocation.setX(m_lastVisibleRect.x()); |
237 currentLocation.setWidth(m_lastVisibleRect.width()); | 258 currentLocation.setWidth(m_lastVisibleRect.width()); |
238 } | 259 } |
239 | 260 |
240 if (currentLocation.y() <= m_lastVisibleRect.y() | 261 if (currentLocation.y() <= m_lastVisibleRect.y() |
241 && currentLocation.y() + currentLocation.height() >= m_lastVisibleRect.y () + m_lastVisibleRect.height()) { | 262 && currentLocation.y() + currentLocation.height() >= m_lastVisibleRect.y () + m_lastVisibleRect.height()) { |
242 currentLocation.setY(m_lastVisibleRect.y()); | 263 currentLocation.setY(m_lastVisibleRect.y()); |
243 currentLocation.setHeight(m_lastVisibleRect.height()); | 264 currentLocation.setHeight(m_lastVisibleRect.height()); |
244 } | 265 } |
245 | 266 |
246 return m_lastVisibleRect.contains(currentLocation); | 267 return m_lastVisibleRect.contains(currentLocation); |
247 } | 268 } |
248 | 269 |
249 bool AutoplayExperimentHelper::maybeStartPlaying() | 270 bool AutoplayExperimentHelper::maybeStartPlaying() |
250 { | 271 { |
251 // See if we're allowed to autoplay now. | 272 // See if we're allowed to autoplay now. |
252 if (!isEligible() || !meetsVisibilityRequirements()) { | 273 if (!isEligible() || !meetsVisibilityRequirements()) { |
253 return false; | 274 return false; |
254 } | 275 } |
255 | 276 |
256 // Start playing! | 277 // Start playing! |
257 prepareToPlay(element().shouldAutoplay() | 278 prepareToAutoplay(client().shouldAutoplay() |
258 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll | 279 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll |
259 : GesturelessPlaybackStartedByPlayMethodAfterScroll); | 280 : GesturelessPlaybackStartedByPlayMethodAfterScroll); |
260 element().playInternal(); | 281 |
282 // Record that this played without a user gesture. | |
283 // This should rarely actually do anything. Usually, playMethodCalled() | |
284 // and becameReadyToPlay will handle it, but toggling muted state can, | |
285 // in some cases, also trigger autoplay if the autoplay attribute is set | |
286 // after the media is ready to play. | |
287 autoplayMediaEncountered(); | |
288 | |
289 client().playInternal(); | |
261 | 290 |
262 return true; | 291 return true; |
263 } | 292 } |
264 | 293 |
265 bool AutoplayExperimentHelper::isEligible() const | 294 bool AutoplayExperimentHelper::isEligible() const |
266 { | 295 { |
267 if (m_mode == Mode::ExperimentOff) | 296 if (m_mode == Mode::ExperimentOff) |
268 return false; | 297 return false; |
269 | 298 |
270 // If no user gesture is required, then the experiment doesn't apply. | 299 // If no user gesture is required, then the experiment doesn't apply. |
271 // This is what prevents us from starting playback more than once. | 300 // 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 | 301 // Since this flag is never set to true once it's cleared, it will block |
273 // the autoplay experiment forever. | 302 // the autoplay experiment forever. |
274 if (!element().isUserGestureRequiredForPlay()) | 303 if (!isUserGestureRequiredForPlay()) |
275 return false; | 304 return false; |
276 | 305 |
277 // Make sure that this is an element of the right type. | 306 // Make sure that this is an element of the right type. |
278 if (!enabled(ForVideo) && isHTMLVideoElement(element())) | 307 if (!enabled(ForVideo) && client().isHTMLVideoElement()) |
279 return false; | 308 return false; |
280 | 309 |
281 if (!enabled(ForAudio) && isHTMLAudioElement(element())) | 310 if (!enabled(ForAudio) && client().isHTMLAudioElement()) |
282 return false; | 311 return false; |
283 | 312 |
284 // If nobody has requested playback, either by the autoplay attribute or | 313 // If nobody has requested playback, either by the autoplay attribute or |
285 // a play() call, then do nothing. | 314 // a play() call, then do nothing. |
286 | 315 |
287 if (!m_playPending && !element().shouldAutoplay()) | 316 if (!m_playPending && !client().shouldAutoplay()) |
288 return false; | 317 return false; |
289 | 318 |
290 // Note that the viewport test always returns false on desktop, which is | 319 // Note that the viewport test always returns false on desktop, which is |
291 // why video-autoplay-experiment.html doesn't check -ifmobile . | 320 // why video-autoplay-experiment.html doesn't check -ifmobile . |
292 if (enabled(IfMobile) | 321 if (enabled(IfMobile) |
293 && !document().viewportDescription().isLegacyViewportType()) | 322 && !client().isLegacyViewportType()) |
294 return false; | 323 return false; |
295 | 324 |
296 // If we require muted media and this is muted, then it is eligible. | 325 // If we require muted media and this is muted, then it is eligible. |
297 if (enabled(IfMuted)) | 326 if (enabled(IfMuted)) |
298 return element().muted(); | 327 return client().muted(); |
299 | 328 |
300 // Element is eligible for gesture override, maybe muted. | 329 // Element is eligible for gesture override, maybe muted. |
301 return true; | 330 return true; |
302 } | 331 } |
303 | 332 |
304 void AutoplayExperimentHelper::muteIfNeeded() | 333 void AutoplayExperimentHelper::muteIfNeeded() |
305 { | 334 { |
306 if (enabled(PlayMuted)) { | 335 if (enabled(PlayMuted)) { |
307 ASSERT(!isEligible()); | 336 ASSERT(!isEligible()); |
308 // If we are actually changing the muted state, then this will call | 337 // If we are actually changing the muted state, then this will call |
309 // mutedChanged(). If isEligible(), then mutedChanged() will try | 338 // mutedChanged(). If isEligible(), then mutedChanged() will try |
310 // to start playback, which we should not do here. | 339 // to start playback, which we should not do here. |
311 element().setMuted(true); | 340 client().setMuted(true); |
312 } | 341 } |
313 } | 342 } |
314 | 343 |
315 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) | 344 void AutoplayExperimentHelper::removeUserGestureRequirement(AutoplayMetrics metr ic) |
316 { | 345 { |
317 element().recordAutoplayMetric(metric); | 346 if (client().isUserGestureRequiredForPlay()) { |
347 m_autoplayDeferredMetric = metric; | |
348 client().removeUserGestureRequirement(); | |
349 } | |
350 } | |
318 | 351 |
352 void AutoplayExperimentHelper::prepareToAutoplay(AutoplayMetrics metric) | |
353 { | |
319 // This also causes !isEligible, so that we don't allow autoplay more than | 354 // This also causes !isEligible, so that we don't allow autoplay more than |
320 // once. Be sure to do this before muteIfNeeded(). | 355 // once. Be sure to do this before muteIfNeeded(). |
321 element().removeUserGestureRequirement(); | 356 // Also note that, at this point, we know that we're goint to start |
357 // playback. However, we still don't record the metric here. Instead, | |
358 // we let playbackStarted() do that later. | |
359 removeUserGestureRequirement(metric); | |
360 | |
361 // Don't bother to call autoplayMediaEncountered, since whoever initiates | |
362 // playback has do it anyway, in case we don't allow autoplay. | |
322 | 363 |
323 unregisterForPositionUpdatesIfNeeded(); | 364 unregisterForPositionUpdatesIfNeeded(); |
324 muteIfNeeded(); | 365 muteIfNeeded(); |
325 | 366 |
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. | 367 // Do not actually start playback here. |
332 } | 368 } |
333 | 369 |
334 Document& AutoplayExperimentHelper::document() const | 370 AutoplayExperimentHelper::Client& AutoplayExperimentHelper::client() const |
335 { | 371 { |
336 return element().document(); | 372 return m_client; |
337 } | |
338 | |
339 HTMLMediaElement& AutoplayExperimentHelper::element() const | |
340 { | |
341 ASSERT(m_element); | |
342 return *m_element; | |
343 } | 373 } |
344 | 374 |
345 AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(const String & mode) | 375 AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(const String & mode) |
346 { | 376 { |
347 Mode value = ExperimentOff; | 377 Mode value = ExperimentOff; |
348 if (mode.contains("-forvideo")) | 378 if (mode.contains("-forvideo")) |
349 value |= ForVideo; | 379 value |= ForVideo; |
350 if (mode.contains("-foraudio")) | 380 if (mode.contains("-foraudio")) |
351 value |= ForAudio; | 381 value |= ForAudio; |
352 if (mode.contains("-ifpagevisible")) | 382 if (mode.contains("-ifpagevisible")) |
353 value |= IfPageVisible; | 383 value |= IfPageVisible; |
354 if (mode.contains("-ifviewport")) | 384 if (mode.contains("-ifviewport")) |
355 value |= IfViewport; | 385 value |= IfViewport; |
356 if (mode.contains("-ifmuted")) | 386 if (mode.contains("-ifmuted")) |
357 value |= IfMuted; | 387 value |= IfMuted; |
358 if (mode.contains("-ifmobile")) | 388 if (mode.contains("-ifmobile")) |
359 value |= IfMobile; | 389 value |= IfMobile; |
360 if (mode.contains("-playmuted")) | 390 if (mode.contains("-playmuted")) |
361 value |= PlayMuted; | 391 value |= PlayMuted; |
362 | 392 |
363 return value; | 393 return value; |
364 } | 394 } |
365 | 395 |
396 void AutoplayExperimentHelper::autoplayMediaEncountered() | |
397 { | |
398 if (!m_autoplayMediaEncountered) { | |
399 m_autoplayMediaEncountered = true; | |
400 recordAutoplayMetric(AutoplayMediaFound); | |
401 } | |
402 } | |
403 | |
404 bool AutoplayExperimentHelper::isUserGestureRequiredForPlay() const | |
405 { | |
406 return client().isUserGestureRequiredForPlay(); | |
407 } | |
408 | |
409 void AutoplayExperimentHelper::recordMetricsBeforePause() | |
410 { | |
411 ASSERT(!client().paused()); | |
412 | |
413 const bool bailout = isBailout(); | |
414 | |
415 // Record that play was paused. We don't care if it was autoplay, | |
416 // play(), or the user manually started it. | |
417 recordAutoplayMetric(AnyPlaybackPaused); | |
418 if (bailout) | |
419 recordAutoplayMetric(AnyPlaybackBailout); | |
420 | |
421 // If this was a gestureless play, then record that separately. | |
422 // These cover attr and play() gestureless starts. | |
423 if (m_waitingForAutoplayPlaybackEnd) { | |
424 m_waitingForAutoplayPlaybackEnd = false; | |
425 | |
426 recordAutoplayMetric(AutoplayPaused); | |
427 | |
428 if (bailout) | |
429 recordAutoplayMetric(AutoplayBailout); | |
430 } | |
431 } | |
432 | |
433 void AutoplayExperimentHelper::playbackStarted() | |
434 { | |
435 recordAutoplayMetric(AnyPlaybackStarted); | |
436 | |
437 if (!m_playbackStartedMetricRecorded) { | |
sof
2016/02/16 08:40:05
if (m_playbackStartedMetricRecorded)
return;
.
liberato (no reviews please)
2016/02/24 18:44:05
Done.
| |
438 m_playbackStartedMetricRecorded = true; | |
439 | |
440 // If this is a gestureless start, record why it was allowed. | |
441 if (!UserGestureIndicator::processingUserGesture()) { | |
442 m_waitingForAutoplayPlaybackEnd = true; | |
443 recordAutoplayMetric(m_autoplayDeferredMetric); | |
444 } | |
445 } | |
446 } | |
447 | |
448 void AutoplayExperimentHelper::playbackEnded() | |
449 { | |
450 recordAutoplayMetric(AnyPlaybackComplete); | |
451 if (m_waitingForAutoplayPlaybackEnd) { | |
sof
2016/02/16 08:40:05
if (!m_waitingForAutoplayPlaybackEnd)
return;
liberato (no reviews please)
2016/02/24 18:44:05
Done.
| |
452 m_waitingForAutoplayPlaybackEnd = false; | |
453 recordAutoplayMetric(AutoplayComplete); | |
454 } | |
455 } | |
456 | |
457 void AutoplayExperimentHelper::recordAutoplayMetric(AutoplayMetrics metric) | |
458 { | |
459 client().recordAutoplayMetric(metric); | |
460 } | |
461 | |
462 bool AutoplayExperimentHelper::isBailout() const | |
463 { | |
464 // We count the user as having bailed-out on the video if they watched | |
465 // less than one minute and less than 50% of it. | |
466 const double playedTime = client().currentTime(); | |
467 const double progress = playedTime / client().duration(); | |
468 return (playedTime < 60) && (progress < 0.5); | |
469 } | |
470 | |
471 void AutoplayExperimentHelper::recordSandboxFailure() | |
472 { | |
473 // We record autoplayMediaEncountered here because we know | |
474 // that the autoplay attempt will fail. | |
475 autoplayMediaEncountered(); | |
476 recordAutoplayMetric(AutoplayDisabledBySandbox); | |
477 } | |
478 | |
479 void AutoplayExperimentHelper::loadingStarted() | |
480 { | |
481 if (m_recordedElement) | |
482 return; | |
483 | |
484 m_recordedElement = true; | |
485 recordAutoplayMetric(client().isHTMLVideoElement() | |
486 ? AnyVideoElement | |
487 : AnyAudioElement); | |
488 } | |
489 | |
366 } // namespace blink | 490 } // namespace blink |
OLD | NEW |