| Index: Source/core/html/AutoplayExperimentHelper.cpp
|
| diff --git a/Source/core/html/AutoplayExperimentHelper.cpp b/Source/core/html/AutoplayExperimentHelper.cpp
|
| index 643eaceb0bda153f928cddc09d7c0cb8cba3cb46..727d8c118e43557c3b391ae216368c8c59d39188 100644
|
| --- a/Source/core/html/AutoplayExperimentHelper.cpp
|
| +++ b/Source/core/html/AutoplayExperimentHelper.cpp
|
| @@ -21,10 +21,17 @@ namespace blink {
|
|
|
| using namespace HTMLNames;
|
|
|
| +// How long do we wait after a scroll event before deciding that no more
|
| +// scroll events are going to arrive?
|
| +static const double viewportTimerPollDelay = 0.5;
|
| +
|
| AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
|
| : m_element(element)
|
| , m_mode(AutoplayExperimentConfig::Mode::Off)
|
| , m_playPending(false)
|
| + , m_registeredWithView(false)
|
| + , m_wasInViewport(false)
|
| + , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired)
|
| {
|
| if (document().settings()) {
|
| m_mode = AutoplayExperimentConfig::fromString(document().settings()->autoplayExperimentMode());
|
| @@ -38,14 +45,19 @@ AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element)
|
|
|
| AutoplayExperimentHelper::~AutoplayExperimentHelper()
|
| {
|
| + unregisterForPositionUpdatesIfNeeded();
|
| }
|
|
|
| void AutoplayExperimentHelper::becameReadyToPlay()
|
| {
|
| // Assuming that we're eligible to override the user gesture requirement,
|
| - // then play.
|
| + // either play if we meet the visibility checks, or install a listener
|
| + // to wait for them to pass.
|
| if (isEligible()) {
|
| - prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
|
| + if (isInViewportIfNeeded())
|
| + prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
|
| + else
|
| + registerForPositionUpdatesIfNeeded();
|
| }
|
| }
|
|
|
| @@ -61,11 +73,19 @@ void AutoplayExperimentHelper::playMethodCalled()
|
| if (isEligible()) {
|
| // Remember that userGestureRequiredForPlay is required for
|
| // us to be eligible for the experiment.
|
| - // We are able to override the gesture requirement now, so
|
| - // do so.
|
| - prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately);
|
| + // If we are able to override the gesture requirement now, then
|
| + // do so. Otherwise, install an event listener if we need one.
|
| + if (isInViewportIfNeeded()) {
|
| + // Override the gesture and play.
|
| + prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately);
|
| + } else {
|
| + // Wait for viewport visibility.
|
| + registerForPositionUpdatesIfNeeded();
|
| + }
|
| }
|
|
|
| + } else if (m_element.isUserGestureRequiredForPlay()) {
|
| + unregisterForPositionUpdatesIfNeeded();
|
| }
|
| }
|
|
|
| @@ -73,18 +93,101 @@ void AutoplayExperimentHelper::pauseMethodCalled()
|
| {
|
| // Don't try to autoplay, if we would have.
|
| m_playPending = false;
|
| + unregisterForPositionUpdatesIfNeeded();
|
| }
|
|
|
| void AutoplayExperimentHelper::mutedChanged()
|
| {
|
| - // In other words, start playing if we just needed 'mute' to autoplay.
|
| + // If we are no longer eligible for the autoplay experiment, then also
|
| + // quit listening for events. If we are eligible, and if we should be
|
| + // playing, then start playing. In other words, start playing if
|
| + // we just needed 'mute' to autoplay.
|
| + if (!isEligible()) {
|
| + unregisterForPositionUpdatesIfNeeded();
|
| + } else {
|
| + // Try to play. If we can't, then install a listener.
|
| + if (!maybeStartPlaying())
|
| + registerForPositionUpdatesIfNeeded();
|
| + }
|
| +}
|
| +
|
| +void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded()
|
| +{
|
| + // If we don't require that the player is in the viewport, then we don't
|
| + // need the listener.
|
| + if (!enabled(AutoplayExperimentConfig::Mode::IfViewport))
|
| + return;
|
| +
|
| + LayoutObject* layoutObject = m_element.layoutObject();
|
| + // TODO(liberato): update tests to include audio.
|
| + // TODO(liberato): fix visibility check for "onscreen".
|
| + if (layoutObject && layoutObject->isMedia()) {
|
| + LayoutMedia* layoutMedia = static_cast<LayoutMedia*>(layoutObject);
|
| + layoutMedia->setRequestPositionUpdates(true);
|
| + m_registeredWithView = true;
|
| + }
|
| +}
|
| +
|
| +void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded()
|
| +{
|
| + if (m_registeredWithView) {
|
| + LayoutObject* obj = m_element.layoutObject();
|
| + if (obj && obj->isMedia()) {
|
| + LayoutMedia* layoutMedia = (LayoutMedia*)obj;
|
| + layoutMedia->setRequestPositionUpdates(false);
|
| + m_registeredWithView = false;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void AutoplayExperimentHelper::positionChanged()
|
| +{
|
| + // Reset the timer to indicate that scrolling has happened
|
| + // recently, and might still be ongoing.
|
| + // Also note that we are called quite often, including when the
|
| + // page becomes visible. That's why we don't bother to register
|
| + // for page visibility changes explicitly.
|
| +
|
| + LocationState curLocation(m_element);
|
| + const bool inViewport = curLocation.isInViewport();
|
| + if (inViewport && !m_wasInViewport) {
|
| + // We have transitioned from not visible to visible. Reset the timer
|
| + // to check if we should start autoplay.
|
| + m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE);
|
| + }
|
| + m_wasInViewport = inViewport;
|
| +}
|
| +
|
| +void AutoplayExperimentHelper::triggerAutoplayViewportCheck()
|
| +{
|
| + viewportTimerFired(nullptr);
|
| +}
|
| +
|
| +void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper>*)
|
| +{
|
| + // Sufficient time has passed since the last scroll that we'll
|
| + // treat it as the end of scroll. Autoplay if we should.
|
| maybeStartPlaying();
|
| }
|
|
|
| +bool AutoplayExperimentHelper::isInViewportIfNeeded()
|
| +{
|
| + // We could check for eligibility here, but we skip it. Some of our
|
| + // callers need to do it separately, and we don't want to check more
|
| + // than we need to.
|
| + // Also remember that page visibility is assumed for clank.
|
| +
|
| + // If viewport visibility isn't required, then it's visible enough.
|
| + if (!enabled(AutoplayExperimentConfig::Mode::IfViewport))
|
| + return true;
|
| +
|
| + return LocationState(m_element).isInViewport();
|
| +}
|
| +
|
| bool AutoplayExperimentHelper::maybeStartPlaying()
|
| {
|
| // See if we're allowed to autoplay now.
|
| - if (!isEligible()) {
|
| + if (!isEligible() || !isInViewportIfNeeded()) {
|
| return false;
|
| }
|
|
|
| @@ -162,6 +265,7 @@ void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric)
|
| // once. Be sure to do this before muteIfNeeded().
|
| m_element.removeUserGestureRequirement();
|
|
|
| + unregisterForPositionUpdatesIfNeeded();
|
| muteIfNeeded();
|
|
|
| // Record that this autoplayed without a user gesture. This is normally
|
| @@ -177,4 +281,63 @@ Document& AutoplayExperimentHelper::document() const
|
| return m_element.document();
|
| }
|
|
|
| +AutoplayExperimentHelper::LocationState::LocationState(Element& element)
|
| + : m_valid(false)
|
| +{
|
| + const LocalDOMWindow* domWindow = element.document().domWindow();
|
| + if (!domWindow)
|
| + return;
|
| +
|
| + // Get the page visibility.
|
| + Frame* frame = domWindow->frame();
|
| + if (!frame)
|
| + return;
|
| +
|
| + Page* page = frame->page();
|
| + if (!page)
|
| + return;
|
| +
|
| + if (!element.layoutObject())
|
| + return;
|
| +
|
| + const LayoutBox* elementBox = element.layoutObject()->enclosingBox();
|
| + if (!elementBox)
|
| + return;
|
| +
|
| + float zoom = elementBox->style()->effectiveZoom();
|
| + IntRect us(elementBox->offsetLeft().toInt()
|
| + , elementBox->offsetTop().toInt()
|
| + , elementBox->clientWidth().toInt()
|
| + , elementBox->clientHeight().toInt());
|
| + IntRect screen(domWindow->scrollX()*zoom, domWindow->scrollY()*zoom, domWindow->innerWidth()*zoom, domWindow->innerHeight()*zoom);
|
| +
|
| + m_visibilityState = page->visibilityState();
|
| + m_element = us;
|
| + m_screen = screen;
|
| + m_valid = true;
|
| +}
|
| +
|
| +bool AutoplayExperimentHelper::LocationState::isInViewport()
|
| +{
|
| + // Check if we're in the viewport.
|
| + // TODO(liberato): fix this.
|
| + return m_valid
|
| + && m_visibilityState == PageVisibilityStateVisible
|
| + && m_screen.contains(m_element);
|
| +}
|
| +
|
| +bool AutoplayExperimentHelper::LocationState::operator==(const LocationState& them) const
|
| +{
|
| + // If either state is not valid, then they are not equal.
|
| + return m_valid && them.valid()
|
| + && m_visibilityState == them.visibilityState()
|
| + && m_screen == them.screen()
|
| + && m_element == them.element();
|
| +}
|
| +
|
| +bool AutoplayExperimentHelper::LocationState::operator!=(const LocationState& them) const
|
| +{
|
| + return !((*this) == them);
|
| +}
|
| +
|
| }
|
|
|