Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "config.h" | |
| 6 #include "core/html/AutoplayExperimentHelper.h" | |
| 7 | |
| 8 #include "core/dom/Document.h" | |
| 9 #include "core/frame/Settings.h" | |
| 10 #include "core/html/HTMLMediaElement.h" | |
| 11 #include "core/layout/LayoutBox.h" | |
| 12 #include "core/layout/LayoutObject.h" | |
| 13 #include "core/layout/LayoutVideo.h" | |
| 14 #include "core/layout/LayoutView.h" | |
| 15 #include "core/page/Page.h" | |
| 16 #include "platform/Logging.h" | |
| 17 #include "platform/UserGestureIndicator.h" | |
| 18 #include "platform/geometry/IntRect.h" | |
| 19 | |
| 20 namespace blink { | |
| 21 | |
| 22 using namespace HTMLNames; | |
| 23 | |
| 24 // How long do we wait after a scroll event before deciding that no more | |
| 25 // scroll events are going to arrive? | |
| 26 static const double viewportTimerPollDelay = 0.5; | |
| 27 | |
| 28 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) | |
| 29 : m_element(element) | |
| 30 , m_mode(AutoplayExperimentConfig::Mode::Off) | |
| 31 , m_playPending(false) | |
| 32 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired) | |
| 33 , m_registeredWithView(false) | |
| 34 { | |
| 35 if (document().settings()) { | |
| 36 m_mode = AutoplayExperimentConfig::fromString(document().settings()->aut oplayExperimentMode()); | |
| 37 | |
| 38 if (m_mode != AutoplayExperimentConfig::Mode::Off) { | |
| 39 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", | |
| 40 m_mode); | |
| 41 } | |
| 42 } | |
| 43 } | |
| 44 | |
| 45 AutoplayExperimentHelper::~AutoplayExperimentHelper() | |
| 46 { | |
| 47 clearEventListenerIfNeeded(); | |
| 48 } | |
| 49 | |
| 50 void AutoplayExperimentHelper::becameReadyToPlay() | |
| 51 { | |
| 52 // Assuming that we're eligible to override the user gesture requirement, | |
| 53 // either play if we meet the visibility checks, or install a listener | |
| 54 // to wait for them to pass. | |
| 55 if (isEligible()) { | |
| 56 if (isInViewportIfNeeded()) | |
| 57 prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); | |
| 58 else | |
| 59 installEventListenerIfNeeded(); | |
| 60 } | |
| 61 } | |
| 62 | |
| 63 void AutoplayExperimentHelper::playMethodCalled() | |
| 64 { | |
| 65 // Set the pending state, even if the play isn't going to be pending. | |
| 66 // Eligibility can change if, for example, the mute status changes. | |
| 67 // Having this set is okay. | |
| 68 m_playPending = true; | |
| 69 | |
| 70 if (!UserGestureIndicator::processingUserGesture()) { | |
| 71 | |
| 72 if (isEligible()) { | |
| 73 // Remember that userGestureRequiredForPlay is required for | |
| 74 // us to be eligible for the experiment. | |
| 75 // If we are able to override the gesture requirement now, then | |
| 76 // do so. Otherwise, install an event listener if we need one. | |
| 77 if (isInViewportIfNeeded()) { | |
| 78 // Override the gesture and play. | |
| 79 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately) ; | |
| 80 } else { | |
| 81 // Wait for viewport visibility. | |
| 82 installEventListenerIfNeeded(); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 } else if (m_element.isUserGestureRequiredForPlay()) { | |
| 87 clearEventListenerIfNeeded(); | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 void AutoplayExperimentHelper::pauseMethodCalled() | |
| 92 { | |
| 93 // Don't try to autoplay, if we would have. | |
| 94 m_playPending = false; | |
| 95 clearEventListenerIfNeeded(); | |
| 96 } | |
| 97 | |
| 98 void AutoplayExperimentHelper::mutedChanged() | |
| 99 { | |
| 100 // If we are no longer eligible for the autoplay experiment, then also | |
| 101 // quit listening for events. If we are eligible, and if we should be | |
| 102 // playing, then start playing. In other words, start playing if | |
| 103 // we just needed 'mute' to autoplay. | |
| 104 if (!isEligible()) { | |
| 105 clearEventListenerIfNeeded(); | |
| 106 } else { | |
| 107 // Try to play. If we can't, then install a listener. | |
| 108 if (!maybeStartPlaying()) | |
| 109 installEventListenerIfNeeded(); | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 void AutoplayExperimentHelper::installEventListenerIfNeeded() | |
| 114 { | |
| 115 // If we don't require that the player is in the viewport, then we don't | |
| 116 // need the listener. | |
| 117 if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)) | |
| 118 return; | |
| 119 | |
| 120 LayoutObject* layoutObject = m_element.layoutObject(); | |
| 121 if (layoutObject && layoutObject->isVideo()) { | |
| 122 LayoutVideo* layoutVideo = (LayoutVideo*)layoutObject; | |
|
philipj_slow
2015/09/02 09:24:11
Use static_cast
liberato (no reviews please)
2015/09/04 06:49:45
Done.
| |
| 123 layoutVideo->setRequestPositionUpdates(true); | |
| 124 // TODO(liberato): do we really need to keep track of this? it's | |
|
philipj_slow
2015/09/02 09:24:11
Is this TODO to be fixed before landing? Who knows
liberato (no reviews please)
2015/09/04 06:49:46
yes, it will. i wanted to put it out for CL with
philipj_slow
2015/09/04 08:42:29
OK, I see this will be done in a separate CL.
| |
| 125 // only to make clearEventListener() faster. | |
| 126 m_registeredWithView = true; | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 void AutoplayExperimentHelper::clearEventListenerIfNeeded() | |
| 131 { | |
| 132 if (m_registeredWithView) { | |
| 133 LayoutObject* obj = m_element.layoutObject(); | |
| 134 if (obj && obj->isVideo()) { | |
| 135 LayoutVideo* video = (LayoutVideo*)obj; | |
|
philipj_slow
2015/09/02 09:24:11
static_cast
liberato (no reviews please)
2015/09/04 06:49:45
thanks, forgot what year it is.
| |
| 136 video->setRequestPositionUpdates(false); | |
| 137 m_registeredWithView = false; | |
| 138 } | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 void AutoplayExperimentHelper::positionChanged() | |
| 143 { | |
| 144 // Reset the timer to indicate that scrolling has happened | |
| 145 // recently, and might still be ongoing. | |
| 146 // Also note that we are called quite often, including when the | |
| 147 // page becomes visible. That's why we don't bother to register | |
| 148 // for page visibility changes explicitly. | |
| 149 | |
| 150 // Since we're called very often, even if our visibility hasn't changed, | |
| 151 // make sure that we only reset the timer if something has moved. | |
| 152 // Otherwise, we will reset the timer every time a video frame plays | |
| 153 // anywhere, or the mouse moves, etc. | |
| 154 // We may want to lower the frequency of this via another timer, so that | |
| 155 // we do no work here. | |
| 156 LocationState curLocation(m_element); | |
| 157 if (curLocation != m_lastLocation) { | |
| 158 m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE); | |
| 159 m_lastLocation = curLocation; | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 void AutoplayExperimentHelper::triggerAutoplayViewportCheck() | |
| 164 { | |
| 165 viewportTimerFired(0); | |
|
philipj_slow
2015/09/02 09:24:11
nullptr
liberato (no reviews please)
2015/09/04 06:49:46
Done.
| |
| 166 } | |
| 167 | |
| 168 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper >*) | |
| 169 { | |
| 170 // Sufficient time has passed since the last scroll that we'll | |
| 171 // treat it as the end of scroll. Autoplay if we should. | |
|
philipj_slow
2015/09/02 09:24:12
I didn't follow the discussion around this very cl
liberato (no reviews please)
2015/09/04 06:49:45
no, unfortunately.
philipj_slow
2015/09/04 08:42:29
Acknowledged.
| |
| 172 maybeStartPlaying(); | |
| 173 } | |
| 174 | |
| 175 bool AutoplayExperimentHelper::isInViewportIfNeeded() | |
| 176 { | |
| 177 // We could check for eligibility here, but we skip it. Some of our | |
| 178 // callers need to do it separately, and we don't want to check more | |
| 179 // than we need to. | |
| 180 // Also remember that page visibility is assumed for clank. | |
| 181 | |
| 182 // If viewport visibility isn't required, then it's visible enough. | |
| 183 if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)) | |
| 184 return true; | |
| 185 | |
| 186 return LocationState(m_element).isInViewport(); | |
| 187 } | |
| 188 | |
| 189 bool AutoplayExperimentHelper::maybeStartPlaying() | |
| 190 { | |
| 191 // See if we're allowed to autoplay now. | |
| 192 if (!isEligible() | |
|
philipj_slow
2015/09/02 09:24:11
A bit much line breaking here, it shouldn't be ver
liberato (no reviews please)
2015/09/04 06:49:46
Done.
| |
| 193 || !isInViewportIfNeeded()) { | |
| 194 return false; | |
| 195 } | |
| 196 | |
| 197 // Start playing! | |
| 198 prepareToPlay(m_element.autoplay() | |
|
philipj_slow
2015/09/02 09:24:11
autoplay() checks the content attribute, but there
liberato (no reviews please)
2015/09/04 06:49:45
good point. i'll add this for clarity, but i thin
philipj_slow
2015/09/04 08:42:29
It should be possible to clear the autoplaying fla
| |
| 199 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll | |
| 200 : GesturelessPlaybackStartedByPlayMethodAfterScroll); | |
| 201 m_element.playInternal(); | |
| 202 | |
| 203 return true; | |
| 204 } | |
| 205 | |
| 206 bool AutoplayExperimentHelper::isEligible() const | |
| 207 { | |
| 208 // If no user gesture is required, then the experiment doesn't apply. | |
| 209 // This is what prevents us from starting playback more than once. | |
| 210 // Since this flag is never set to true once it's cleared, it will block | |
| 211 // the autoplay experiment forever. | |
| 212 if (!m_element.isUserGestureRequiredForPlay()) | |
| 213 return false; | |
| 214 | |
| 215 if (m_mode == AutoplayExperimentConfig::Mode::Off) | |
| 216 return false; | |
| 217 | |
| 218 // If nobody has requested playback, either by the autoplay attribute or | |
| 219 // a play() call, then do nothing. | |
| 220 if (!m_playPending && !m_element.autoplay()) | |
|
philipj_slow
2015/09/02 09:24:11
shouldAutoplay() here too. To write a test for the
liberato (no reviews please)
2015/09/04 06:49:46
Done, including test.
| |
| 221 return false; | |
| 222 | |
| 223 // If the video is already playing, then do nothing. Note that there | |
| 224 // is not a path where a user gesture is required but the video is | |
| 225 // playing. However, we check for completeness. | |
| 226 if (!m_element.paused()) | |
| 227 return false; | |
| 228 | |
| 229 // Note that the viewport test always returns false on desktop, which is | |
| 230 // why video-autoplay-experiment.html doesn't check -ifmobile . | |
| 231 if (enabled(AutoplayExperimentConfig::Mode::IfMobile) | |
| 232 && !document().viewportDescription().isLegacyViewportType()) | |
| 233 return false; | |
| 234 | |
| 235 if (enabled(AutoplayExperimentConfig::Mode::IfMuted)) { | |
| 236 // If media is muted, then autoplay when it comes into view. | |
|
philipj_slow
2015/09/02 09:24:11
Move this outside the if and remove {} for consist
liberato (no reviews please)
2015/09/04 06:49:45
Done.
| |
| 237 return m_element.fastHasAttribute(mutedAttr) || m_element.muted(); | |
| 238 } | |
| 239 | |
| 240 // Autoplay when it comes into view (if needed), maybe muted. | |
| 241 return true; | |
| 242 } | |
| 243 | |
| 244 void AutoplayExperimentHelper::muteIfNeeded() | |
| 245 { | |
| 246 if (enabled(AutoplayExperimentConfig::Mode::PlayMuted)) { | |
| 247 ASSERT(!isEligible()); | |
| 248 // If we are actually changing the muted state, then this will call | |
| 249 // mutedChanged(). If isEligible(), then mutedChanged() will try | |
| 250 // to start playback, which we should not do here. | |
| 251 m_element.setMuted(true); | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) | |
| 256 { | |
| 257 m_element.recordAutoplayMetric(metric); | |
| 258 | |
| 259 // This also causes !isEligible, so that we don't alow autoplay more than | |
|
philipj_slow
2015/09/02 09:24:11
s/alow/allow/
liberato (no reviews please)
2015/09/04 06:49:45
Done.
| |
| 260 // once. Be sure to do this before muteIfNeeded(). | |
| 261 m_element.removeUserGestureRequirement(); | |
| 262 | |
| 263 clearEventListenerIfNeeded(); | |
| 264 muteIfNeeded(); | |
| 265 | |
| 266 // Record that this autoplayed without a user gesture. This is normally | |
| 267 // set when we discover an autoplay attribute, but we include all cases | |
| 268 // where playback started without a user gesture, e.g., play(). | |
| 269 m_element.setInitialPlayWithoutUserGestures(true); | |
| 270 | |
| 271 // Do not actually start playback here. | |
| 272 } | |
| 273 | |
| 274 Document& AutoplayExperimentHelper::document() const | |
| 275 { | |
| 276 return m_element.document(); | |
| 277 } | |
| 278 | |
| 279 AutoplayExperimentHelper::LocationState::LocationState(Element& element) | |
| 280 : m_valid(false) | |
| 281 { | |
| 282 const LocalDOMWindow* domWindow = element.document().domWindow(); | |
| 283 if (!domWindow) | |
| 284 return; | |
| 285 | |
| 286 // Get the page visibility. | |
| 287 Frame* frame = domWindow->frame(); | |
| 288 if (!frame) | |
| 289 return; | |
| 290 | |
| 291 Page* page = frame->page(); | |
| 292 if (!page) | |
| 293 return; | |
| 294 | |
| 295 if (!element.layoutObject()) | |
| 296 return; | |
| 297 | |
| 298 const LayoutBox* elementBox = element.layoutObject()->enclosingBox(); | |
| 299 if (!elementBox) | |
| 300 return; | |
| 301 | |
| 302 float zoom = elementBox->style()->effectiveZoom(); | |
| 303 IntRect us(elementBox->offsetLeft().toInt() | |
| 304 , elementBox->offsetTop().toInt() | |
| 305 , elementBox->clientWidth().toInt() | |
| 306 , elementBox->clientHeight().toInt()); | |
| 307 IntRect screen(domWindow->scrollX()*zoom, domWindow->scrollY()*zoom, domWind ow->innerWidth()*zoom, domWindow->innerHeight()*zoom); | |
| 308 | |
| 309 m_visibilityState = page->visibilityState(); | |
| 310 m_element = us; | |
| 311 m_screen = screen; | |
| 312 m_valid = true; | |
| 313 } | |
| 314 | |
| 315 bool AutoplayExperimentHelper::LocationState::isInViewport() | |
| 316 { | |
| 317 // Check if we're in the viewport. | |
| 318 return m_valid | |
| 319 && m_visibilityState == PageVisibilityStateVisible | |
| 320 && m_screen.contains(m_element); | |
| 321 } | |
| 322 | |
| 323 bool AutoplayExperimentHelper::LocationState::operator==(const LocationState& th em) const | |
| 324 { | |
| 325 // If either state is not valid, then they are not equal. | |
| 326 return m_valid && them.valid() | |
| 327 && m_visibilityState == them.visibilityState() | |
| 328 && m_screen == them.screen() | |
| 329 && m_element == them.element(); | |
| 330 } | |
| 331 | |
| 332 bool AutoplayExperimentHelper::LocationState::operator!=(const LocationState& th em) const | |
| 333 { | |
| 334 return !((*this) == them); | |
| 335 } | |
| 336 | |
| 337 } | |
| OLD | NEW |