| 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 "config.h" | 5 #include "config.h" | 
| 6 #include "core/html/AutoplayExperimentHelper.h" | 6 #include "core/html/AutoplayExperimentHelper.h" | 
| 7 | 7 | 
| 8 #include "core/dom/Document.h" | 8 #include "core/dom/Document.h" | 
| 9 #include "core/frame/Settings.h" | 9 #include "core/frame/Settings.h" | 
| 10 #include "core/html/HTMLMediaElement.h" | 10 #include "core/html/HTMLMediaElement.h" | 
| 11 #include "core/layout/LayoutBox.h" | 11 #include "core/layout/LayoutBox.h" | 
| 12 #include "core/layout/LayoutObject.h" | 12 #include "core/layout/LayoutObject.h" | 
| 13 #include "core/layout/LayoutVideo.h" | 13 #include "core/layout/LayoutVideo.h" | 
| 14 #include "core/layout/LayoutView.h" | 14 #include "core/layout/LayoutView.h" | 
| 15 #include "core/page/Page.h" | 15 #include "core/page/Page.h" | 
| 16 #include "platform/Logging.h" | 16 #include "platform/Logging.h" | 
| 17 #include "platform/UserGestureIndicator.h" | 17 #include "platform/UserGestureIndicator.h" | 
| 18 #include "platform/geometry/IntRect.h" | 18 #include "platform/geometry/IntRect.h" | 
| 19 | 19 | 
| 20 namespace blink { | 20 namespace blink { | 
| 21 | 21 | 
| 22 using namespace HTMLNames; | 22 using namespace HTMLNames; | 
| 23 | 23 | 
|  | 24 // How long do we wait after a scroll event before deciding that no more | 
|  | 25 // scroll events are going to arrive? | 
|  | 26 static const double viewportTimerPollDelay = 0.5; | 
|  | 27 | 
| 24 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) | 28 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) | 
| 25     : m_element(element) | 29     : m_element(element) | 
| 26     , m_mode(AutoplayExperimentConfig::Mode::Off) | 30     , m_mode(AutoplayExperimentConfig::Mode::Off) | 
| 27     , m_playPending(false) | 31     , m_playPending(false) | 
|  | 32     , m_registeredWithView(false) | 
|  | 33     , m_wasInViewport(false) | 
|  | 34     , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired) | 
| 28 { | 35 { | 
| 29     if (document().settings()) { | 36     if (document().settings()) { | 
| 30         m_mode = AutoplayExperimentConfig::fromString(document().settings()->aut
     oplayExperimentMode()); | 37         m_mode = AutoplayExperimentConfig::fromString(document().settings()->aut
     oplayExperimentMode()); | 
| 31 | 38 | 
| 32         if (m_mode != AutoplayExperimentConfig::Mode::Off) { | 39         if (m_mode != AutoplayExperimentConfig::Mode::Off) { | 
| 33             WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", | 40             WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", | 
| 34                 m_mode); | 41                 m_mode); | 
| 35         } | 42         } | 
| 36     } | 43     } | 
| 37 } | 44 } | 
| 38 | 45 | 
| 39 AutoplayExperimentHelper::~AutoplayExperimentHelper() | 46 AutoplayExperimentHelper::~AutoplayExperimentHelper() | 
| 40 { | 47 { | 
|  | 48     unregisterForPositionUpdatesIfNeeded(); | 
| 41 } | 49 } | 
| 42 | 50 | 
| 43 void AutoplayExperimentHelper::becameReadyToPlay() | 51 void AutoplayExperimentHelper::becameReadyToPlay() | 
| 44 { | 52 { | 
| 45     // Assuming that we're eligible to override the user gesture requirement, | 53     // Assuming that we're eligible to override the user gesture requirement, | 
| 46     // then play. | 54     // either play if we meet the visibility checks, or install a listener | 
|  | 55     // to wait for them to pass. | 
| 47     if (isEligible()) { | 56     if (isEligible()) { | 
| 48         prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); | 57         if (isInViewportIfNeeded()) | 
|  | 58             prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); | 
|  | 59         else | 
|  | 60             registerForPositionUpdatesIfNeeded(); | 
| 49     } | 61     } | 
| 50 } | 62 } | 
| 51 | 63 | 
| 52 void AutoplayExperimentHelper::playMethodCalled() | 64 void AutoplayExperimentHelper::playMethodCalled() | 
| 53 { | 65 { | 
| 54     // Set the pending state, even if the play isn't going to be pending. | 66     // Set the pending state, even if the play isn't going to be pending. | 
| 55     // Eligibility can change if, for example, the mute status changes. | 67     // Eligibility can change if, for example, the mute status changes. | 
| 56     // Having this set is okay. | 68     // Having this set is okay. | 
| 57     m_playPending = true; | 69     m_playPending = true; | 
| 58 | 70 | 
| 59     if (!UserGestureIndicator::processingUserGesture()) { | 71     if (!UserGestureIndicator::processingUserGesture()) { | 
| 60 | 72 | 
| 61         if (isEligible()) { | 73         if (isEligible()) { | 
| 62             // Remember that userGestureRequiredForPlay is required for | 74             // Remember that userGestureRequiredForPlay is required for | 
| 63             // us to be eligible for the experiment. | 75             // us to be eligible for the experiment. | 
| 64             // We are able to override the gesture requirement now, so | 76             // If we are able to override the gesture requirement now, then | 
| 65             // do so. | 77             // do so.  Otherwise, install an event listener if we need one. | 
| 66             prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately); | 78             if (isInViewportIfNeeded()) { | 
|  | 79                 // Override the gesture and play. | 
|  | 80                 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately)
     ; | 
|  | 81             } else { | 
|  | 82                 // Wait for viewport visibility. | 
|  | 83                 registerForPositionUpdatesIfNeeded(); | 
|  | 84             } | 
| 67         } | 85         } | 
| 68 | 86 | 
|  | 87     } else if (m_element.isUserGestureRequiredForPlay()) { | 
|  | 88         unregisterForPositionUpdatesIfNeeded(); | 
| 69     } | 89     } | 
| 70 } | 90 } | 
| 71 | 91 | 
| 72 void AutoplayExperimentHelper::pauseMethodCalled() | 92 void AutoplayExperimentHelper::pauseMethodCalled() | 
| 73 { | 93 { | 
| 74     // Don't try to autoplay, if we would have. | 94     // Don't try to autoplay, if we would have. | 
| 75     m_playPending = false; | 95     m_playPending = false; | 
|  | 96     unregisterForPositionUpdatesIfNeeded(); | 
| 76 } | 97 } | 
| 77 | 98 | 
| 78 void AutoplayExperimentHelper::mutedChanged() | 99 void AutoplayExperimentHelper::mutedChanged() | 
| 79 { | 100 { | 
| 80     // In other words, start playing if we just needed 'mute' to autoplay. | 101     // 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 | 
|  | 103     // playing, then start playing.  In other words, start playing if | 
|  | 104     // we just needed 'mute' to autoplay. | 
|  | 105     if (!isEligible()) { | 
|  | 106         unregisterForPositionUpdatesIfNeeded(); | 
|  | 107     } else { | 
|  | 108         // Try to play.  If we can't, then install a listener. | 
|  | 109         if (!maybeStartPlaying()) | 
|  | 110             registerForPositionUpdatesIfNeeded(); | 
|  | 111     } | 
|  | 112 } | 
|  | 113 | 
|  | 114 void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded() | 
|  | 115 { | 
|  | 116     // If we don't require that the player is in the viewport, then we don't | 
|  | 117     // need the listener. | 
|  | 118     if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)) | 
|  | 119         return; | 
|  | 120 | 
|  | 121     LayoutObject* layoutObject = m_element.layoutObject(); | 
|  | 122         // TODO(liberato): update tests to include audio. | 
|  | 123         // TODO(liberato): fix visibility check for "onscreen". | 
|  | 124     if (layoutObject && layoutObject->isMedia()) { | 
|  | 125         LayoutMedia* layoutMedia = static_cast<LayoutMedia*>(layoutObject); | 
|  | 126         layoutMedia->setRequestPositionUpdates(true); | 
|  | 127         m_registeredWithView = true; | 
|  | 128     } | 
|  | 129 } | 
|  | 130 | 
|  | 131 void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded() | 
|  | 132 { | 
|  | 133     if (m_registeredWithView) { | 
|  | 134         LayoutObject* obj = m_element.layoutObject(); | 
|  | 135         if (obj && obj->isMedia()) { | 
|  | 136             LayoutMedia* layoutMedia = (LayoutMedia*)obj; | 
|  | 137             layoutMedia->setRequestPositionUpdates(false); | 
|  | 138             m_registeredWithView = false; | 
|  | 139         } | 
|  | 140     } | 
|  | 141 } | 
|  | 142 | 
|  | 143 void AutoplayExperimentHelper::positionChanged() | 
|  | 144 { | 
|  | 145     // Reset the timer to indicate that scrolling has happened | 
|  | 146     // recently, and might still be ongoing. | 
|  | 147     // Also note that we are called quite often, including when the | 
|  | 148     // page becomes visible.  That's why we don't bother to register | 
|  | 149     // for page visibility changes explicitly. | 
|  | 150 | 
|  | 151     LocationState curLocation(m_element); | 
|  | 152     const bool inViewport = curLocation.isInViewport(); | 
|  | 153     if (inViewport && !m_wasInViewport) { | 
|  | 154         // We have transitioned from not visible to visible.  Reset the timer | 
|  | 155         // to check if we should start autoplay. | 
|  | 156         m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE); | 
|  | 157     } | 
|  | 158     m_wasInViewport = inViewport; | 
|  | 159 } | 
|  | 160 | 
|  | 161 void AutoplayExperimentHelper::triggerAutoplayViewportCheck() | 
|  | 162 { | 
|  | 163     viewportTimerFired(nullptr); | 
|  | 164 } | 
|  | 165 | 
|  | 166 void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper
     >*) | 
|  | 167 { | 
|  | 168     // Sufficient time has passed since the last scroll that we'll | 
|  | 169     // treat it as the end of scroll.  Autoplay if we should. | 
| 81     maybeStartPlaying(); | 170     maybeStartPlaying(); | 
| 82 } | 171 } | 
| 83 | 172 | 
|  | 173 bool AutoplayExperimentHelper::isInViewportIfNeeded() | 
|  | 174 { | 
|  | 175     // We could check for eligibility here, but we skip it.  Some of our | 
|  | 176     // callers need to do it separately, and we don't want to check more | 
|  | 177     // than we need to. | 
|  | 178     // Also remember that page visibility is assumed for clank. | 
|  | 179 | 
|  | 180     // If viewport visibility isn't required, then it's visible enough. | 
|  | 181     if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)) | 
|  | 182         return true; | 
|  | 183 | 
|  | 184     return LocationState(m_element).isInViewport(); | 
|  | 185 } | 
|  | 186 | 
| 84 bool AutoplayExperimentHelper::maybeStartPlaying() | 187 bool AutoplayExperimentHelper::maybeStartPlaying() | 
| 85 { | 188 { | 
| 86     // See if we're allowed to autoplay now. | 189     // See if we're allowed to autoplay now. | 
| 87     if (!isEligible()) { | 190     if (!isEligible() || !isInViewportIfNeeded()) { | 
| 88         return false; | 191         return false; | 
| 89     } | 192     } | 
| 90 | 193 | 
| 91     // Start playing! | 194     // Start playing! | 
| 92     prepareToPlay(m_element.shouldAutoplay() | 195     prepareToPlay(m_element.shouldAutoplay() | 
| 93         ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll | 196         ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll | 
| 94         : GesturelessPlaybackStartedByPlayMethodAfterScroll); | 197         : GesturelessPlaybackStartedByPlayMethodAfterScroll); | 
| 95     m_element.playInternal(); | 198     m_element.playInternal(); | 
| 96 | 199 | 
| 97     return true; | 200     return true; | 
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 155 } | 258 } | 
| 156 | 259 | 
| 157 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) | 260 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) | 
| 158 { | 261 { | 
| 159     m_element.recordAutoplayMetric(metric); | 262     m_element.recordAutoplayMetric(metric); | 
| 160 | 263 | 
| 161     // This also causes !isEligible, so that we don't allow autoplay more than | 264     // This also causes !isEligible, so that we don't allow autoplay more than | 
| 162     // once.  Be sure to do this before muteIfNeeded(). | 265     // once.  Be sure to do this before muteIfNeeded(). | 
| 163     m_element.removeUserGestureRequirement(); | 266     m_element.removeUserGestureRequirement(); | 
| 164 | 267 | 
|  | 268     unregisterForPositionUpdatesIfNeeded(); | 
| 165     muteIfNeeded(); | 269     muteIfNeeded(); | 
| 166 | 270 | 
| 167     // Record that this autoplayed without a user gesture.  This is normally | 271     // Record that this autoplayed without a user gesture.  This is normally | 
| 168     // set when we discover an autoplay attribute, but we include all cases | 272     // set when we discover an autoplay attribute, but we include all cases | 
| 169     // where playback started without a user gesture, e.g., play(). | 273     // where playback started without a user gesture, e.g., play(). | 
| 170     m_element.setInitialPlayWithoutUserGestures(true); | 274     m_element.setInitialPlayWithoutUserGestures(true); | 
| 171 | 275 | 
| 172     // Do not actually start playback here. | 276     // Do not actually start playback here. | 
| 173 } | 277 } | 
| 174 | 278 | 
| 175 Document& AutoplayExperimentHelper::document() const | 279 Document& AutoplayExperimentHelper::document() const | 
| 176 { | 280 { | 
| 177     return m_element.document(); | 281     return m_element.document(); | 
| 178 } | 282 } | 
| 179 | 283 | 
|  | 284 AutoplayExperimentHelper::LocationState::LocationState(Element& element) | 
|  | 285     : m_valid(false) | 
|  | 286 { | 
|  | 287     const LocalDOMWindow* domWindow = element.document().domWindow(); | 
|  | 288     if (!domWindow) | 
|  | 289         return; | 
|  | 290 | 
|  | 291     // Get the page visibility. | 
|  | 292     Frame* frame = domWindow->frame(); | 
|  | 293     if (!frame) | 
|  | 294         return; | 
|  | 295 | 
|  | 296     Page* page = frame->page(); | 
|  | 297     if (!page) | 
|  | 298         return; | 
|  | 299 | 
|  | 300     if (!element.layoutObject()) | 
|  | 301         return; | 
|  | 302 | 
|  | 303     const LayoutBox* elementBox = element.layoutObject()->enclosingBox(); | 
|  | 304     if (!elementBox) | 
|  | 305         return; | 
|  | 306 | 
|  | 307     float zoom = elementBox->style()->effectiveZoom(); | 
|  | 308     IntRect us(elementBox->offsetLeft().toInt() | 
|  | 309         , elementBox->offsetTop().toInt() | 
|  | 310         , elementBox->clientWidth().toInt() | 
|  | 311         , elementBox->clientHeight().toInt()); | 
|  | 312     IntRect screen(domWindow->scrollX()*zoom, domWindow->scrollY()*zoom, domWind
     ow->innerWidth()*zoom, domWindow->innerHeight()*zoom); | 
|  | 313 | 
|  | 314     m_visibilityState = page->visibilityState(); | 
|  | 315     m_element = us; | 
|  | 316     m_screen = screen; | 
|  | 317     m_valid = true; | 
| 180 } | 318 } | 
|  | 319 | 
|  | 320 bool AutoplayExperimentHelper::LocationState::isInViewport() | 
|  | 321 { | 
|  | 322     // Check if we're in the viewport. | 
|  | 323     // TODO(liberato): fix this. | 
|  | 324     return m_valid | 
|  | 325         && m_visibilityState == PageVisibilityStateVisible | 
|  | 326         && m_screen.contains(m_element); | 
|  | 327 } | 
|  | 328 | 
|  | 329 bool AutoplayExperimentHelper::LocationState::operator==(const LocationState& th
     em) const | 
|  | 330 { | 
|  | 331     // If either state is not valid, then they are not equal. | 
|  | 332     return m_valid && them.valid() | 
|  | 333         && m_visibilityState == them.visibilityState() | 
|  | 334         && m_screen == them.screen() | 
|  | 335         && m_element == them.element(); | 
|  | 336 } | 
|  | 337 | 
|  | 338 bool AutoplayExperimentHelper::LocationState::operator!=(const LocationState& th
     em) const | 
|  | 339 { | 
|  | 340     return !((*this) == them); | 
|  | 341 } | 
|  | 342 | 
|  | 343 } | 
| OLD | NEW | 
|---|