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