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