| 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/FrameView.h" | 9 #include "core/frame/FrameView.h" |
| 10 #include "core/frame/Settings.h" | 10 #include "core/frame/Settings.h" |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 | 24 |
| 25 // Seconds to wait after a video has stopped moving before playing it. | 25 // Seconds to wait after a video has stopped moving before playing it. |
| 26 static const double kViewportTimerPollDelay = 0.5; | 26 static const double kViewportTimerPollDelay = 0.5; |
| 27 | 27 |
| 28 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) | 28 AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) |
| 29 : m_element(&element) | 29 : m_element(&element) |
| 30 , m_mode(Mode::ExperimentOff) | 30 , m_mode(Mode::ExperimentOff) |
| 31 , m_playPending(false) | 31 , m_playPending(false) |
| 32 , m_registeredWithLayoutObject(false) | 32 , m_registeredWithLayoutObject(false) |
| 33 , m_wasInViewport(false) | 33 , m_wasInViewport(false) |
| 34 , m_autoplayMediaCounted(false) |
| 35 , m_initialPlayWithoutUserGesture(false) |
| 36 , m_recordedElement(false) |
| 34 , m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity()) | 37 , m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity()) |
| 35 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired) | 38 , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired) |
| 39 , m_autoplayDeferredMetric(GesturelessPlaybackUnknownReason) |
| 36 { | 40 { |
| 37 if (document().settings()) { | 41 if (document().settings()) { |
| 38 m_mode = fromString(document().settings()->autoplayExperimentMode()); | 42 m_mode = fromString(document().settings()->autoplayExperimentMode()); |
| 39 | 43 |
| 40 if (m_mode != Mode::ExperimentOff) { | 44 if (m_mode != Mode::ExperimentOff) { |
| 41 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", | 45 WTF_LOG(Media, "HTMLMediaElement: autoplay experiment set to %d", |
| 42 m_mode); | 46 m_mode); |
| 43 } | 47 } |
| 44 } | 48 } |
| 45 } | 49 } |
| 46 | 50 |
| 47 AutoplayExperimentHelper::~AutoplayExperimentHelper() | 51 AutoplayExperimentHelper::~AutoplayExperimentHelper() |
| 48 { | 52 { |
| 49 unregisterForPositionUpdatesIfNeeded(); | 53 unregisterForPositionUpdatesIfNeeded(); |
| 50 } | 54 } |
| 51 | 55 |
| 52 void AutoplayExperimentHelper::becameReadyToPlay() | 56 void AutoplayExperimentHelper::becameReadyToPlay() |
| 53 { | 57 { |
| 54 // Assuming that we're eligible to override the user gesture requirement, | 58 // Assuming that we're eligible to override the user gesture requirement, |
| 55 // either play if we meet the visibility checks, or install a listener | 59 // either play if we meet the visibility checks, or install a listener |
| 56 // to wait for them to pass. | 60 // to wait for them to pass. We do not actually start playback; our |
| 61 // caller must do that. |
| 57 if (isEligible()) { | 62 if (isEligible()) { |
| 58 if (meetsVisibilityRequirements()) | 63 if (meetsVisibilityRequirements()) |
| 59 prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); | 64 prepareToAutoplay(GesturelessPlaybackStartedByAutoplayFlagImmediatel
y); |
| 60 else | 65 else |
| 61 registerForPositionUpdatesIfNeeded(); | 66 registerForPositionUpdatesIfNeeded(); |
| 62 } | 67 } |
| 68 |
| 69 autoplayMediaEncountered(); |
| 63 } | 70 } |
| 64 | 71 |
| 65 void AutoplayExperimentHelper::playMethodCalled() | 72 void AutoplayExperimentHelper::playMethodCalled() |
| 66 { | 73 { |
| 67 // Set the pending state, even if the play isn't going to be pending. | 74 // 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. | 75 // Eligibility can change if, for example, the mute status changes. |
| 69 // Having this set is okay. | 76 // Having this set is okay. |
| 70 m_playPending = true; | 77 m_playPending = true; |
| 71 | 78 |
| 72 if (!UserGestureIndicator::processingUserGesture()) { | 79 if (!UserGestureIndicator::processingUserGesture()) { |
| 73 | |
| 74 if (isEligible()) { | 80 if (isEligible()) { |
| 75 // Remember that userGestureRequiredForPlay is required for | 81 // Remember that userGestureRequiredForPlay is required for |
| 76 // us to be eligible for the experiment. | 82 // us to be eligible for the experiment. |
| 77 // If we are able to override the gesture requirement now, then | 83 // If we are able to override the gesture requirement now, then |
| 78 // do so. Otherwise, install an event listener if we need one. | 84 // do so. Otherwise, install an event listener if we need one. |
| 79 if (meetsVisibilityRequirements()) { | 85 if (meetsVisibilityRequirements()) { |
| 80 // Override the gesture and play. | 86 // Override the gesture and assume that play() will succeed. |
| 81 prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately)
; | 87 prepareToAutoplay(GesturelessPlaybackStartedByPlayMethodImmediat
ely); |
| 82 } else { | 88 } else { |
| 83 // Wait for viewport visibility. | 89 // Wait for viewport visibility. |
| 84 registerForPositionUpdatesIfNeeded(); | 90 registerForPositionUpdatesIfNeeded(); |
| 85 } | 91 } |
| 86 } | 92 } |
| 87 | 93 |
| 88 } else if (element().isUserGestureRequiredForPlay()) { | 94 // Now that we might have also overridden the gesture requirement, |
| 95 // record the autoplay attempt. |
| 96 autoplayMediaEncountered(); |
| 97 } else if (isUserGestureRequiredForPlay()) { |
| 98 if (m_autoplayMediaCounted) |
| 99 recordAutoplayMetric(AutoplayManualStart); |
| 100 // Don't let future gestureless playbacks affect metrics. |
| 101 m_autoplayMediaCounted = true; |
| 102 |
| 89 unregisterForPositionUpdatesIfNeeded(); | 103 unregisterForPositionUpdatesIfNeeded(); |
| 90 } | 104 } |
| 91 } | 105 } |
| 92 | 106 |
| 93 void AutoplayExperimentHelper::pauseMethodCalled() | 107 void AutoplayExperimentHelper::pauseMethodCalled() |
| 94 { | 108 { |
| 95 // Don't try to autoplay, if we would have. | 109 // Don't try to autoplay, if we would have. |
| 96 m_playPending = false; | 110 m_playPending = false; |
| 97 unregisterForPositionUpdatesIfNeeded(); | 111 unregisterForPositionUpdatesIfNeeded(); |
| 112 |
| 113 if (!element().paused()) |
| 114 recordMetricsBeforePause(); |
| 115 } |
| 116 |
| 117 void AutoplayExperimentHelper::loadMethodCalled() |
| 118 { |
| 119 if (!element().paused()) |
| 120 recordMetricsBeforePause(); |
| 121 |
| 122 if (UserGestureIndicator::processingUserGesture() && isUserGestureRequiredFo
rPlay()) { |
| 123 recordAutoplayMetric(AutoplayEnabledThroughLoad); |
| 124 element().removeUserGestureRequirement(GesturelessPlaybackEnabledByLoad)
; |
| 125 // While usergesture-initiated load()s technically count as autoplayed, |
| 126 // they don't feel like such to the users and hence we don't want to |
| 127 // count them for the purposes of metrics. |
| 128 m_autoplayMediaCounted = true; |
| 129 } |
| 98 } | 130 } |
| 99 | 131 |
| 100 void AutoplayExperimentHelper::mutedChanged() | 132 void AutoplayExperimentHelper::mutedChanged() |
| 101 { | 133 { |
| 102 // If we are no longer eligible for the autoplay experiment, then also | 134 // 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 | 135 // quit listening for events. If we are eligible, and if we should be |
| 104 // playing, then start playing. In other words, start playing if | 136 // playing, then start playing. In other words, start playing if |
| 105 // we just needed 'mute' to autoplay. | 137 // we just needed 'mute' to autoplay. |
| 106 if (!isEligible()) { | 138 if (!isEligible()) { |
| 107 unregisterForPositionUpdatesIfNeeded(); | 139 unregisterForPositionUpdatesIfNeeded(); |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 } | 280 } |
| 249 | 281 |
| 250 bool AutoplayExperimentHelper::maybeStartPlaying() | 282 bool AutoplayExperimentHelper::maybeStartPlaying() |
| 251 { | 283 { |
| 252 // See if we're allowed to autoplay now. | 284 // See if we're allowed to autoplay now. |
| 253 if (!isEligible() || !meetsVisibilityRequirements()) { | 285 if (!isEligible() || !meetsVisibilityRequirements()) { |
| 254 return false; | 286 return false; |
| 255 } | 287 } |
| 256 | 288 |
| 257 // Start playing! | 289 // Start playing! |
| 258 prepareToPlay(element().shouldAutoplay() | 290 prepareToAutoplay(element().shouldAutoplay() |
| 259 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll | 291 ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll |
| 260 : GesturelessPlaybackStartedByPlayMethodAfterScroll); | 292 : GesturelessPlaybackStartedByPlayMethodAfterScroll); |
| 293 |
| 294 // Record that this played without a user gesture. |
| 295 autoplayMediaEncountered(); |
| 296 |
| 261 element().playInternal(); | 297 element().playInternal(); |
| 262 | 298 |
| 263 return true; | 299 return true; |
| 264 } | 300 } |
| 265 | 301 |
| 266 bool AutoplayExperimentHelper::isEligible() const | 302 bool AutoplayExperimentHelper::isEligible() const |
| 267 { | 303 { |
| 268 if (m_mode == Mode::ExperimentOff) | 304 if (m_mode == Mode::ExperimentOff) |
| 269 return false; | 305 return false; |
| 270 | 306 |
| 271 // If no user gesture is required, then the experiment doesn't apply. | 307 // If no user gesture is required, then the experiment doesn't apply. |
| 272 // This is what prevents us from starting playback more than once. | 308 // 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 | 309 // Since this flag is never set to true once it's cleared, it will block |
| 274 // the autoplay experiment forever. | 310 // the autoplay experiment forever. |
| 275 if (!element().isUserGestureRequiredForPlay()) | 311 if (!isUserGestureRequiredForPlay()) |
| 276 return false; | 312 return false; |
| 277 | 313 |
| 278 // Make sure that this is an element of the right type. | 314 // Make sure that this is an element of the right type. |
| 279 if (!enabled(ForVideo) && isHTMLVideoElement(element())) | 315 if (!enabled(ForVideo) && isHTMLVideoElement(element())) |
| 280 return false; | 316 return false; |
| 281 | 317 |
| 282 if (!enabled(ForAudio) && isHTMLAudioElement(element())) | 318 if (!enabled(ForAudio) && isHTMLAudioElement(element())) |
| 283 return false; | 319 return false; |
| 284 | 320 |
| 285 // If nobody has requested playback, either by the autoplay attribute or | 321 // If nobody has requested playback, either by the autoplay attribute or |
| (...skipping 20 matching lines...) Expand all Loading... |
| 306 { | 342 { |
| 307 if (enabled(PlayMuted)) { | 343 if (enabled(PlayMuted)) { |
| 308 ASSERT(!isEligible()); | 344 ASSERT(!isEligible()); |
| 309 // If we are actually changing the muted state, then this will call | 345 // If we are actually changing the muted state, then this will call |
| 310 // mutedChanged(). If isEligible(), then mutedChanged() will try | 346 // mutedChanged(). If isEligible(), then mutedChanged() will try |
| 311 // to start playback, which we should not do here. | 347 // to start playback, which we should not do here. |
| 312 element().setMuted(true); | 348 element().setMuted(true); |
| 313 } | 349 } |
| 314 } | 350 } |
| 315 | 351 |
| 316 void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) | 352 void AutoplayExperimentHelper::prepareToAutoplay(AutoplayMetrics metric) |
| 317 { | 353 { |
| 318 element().recordAutoplayMetric(metric); | |
| 319 | |
| 320 // This also causes !isEligible, so that we don't allow autoplay more than | 354 // This also causes !isEligible, so that we don't allow autoplay more than |
| 321 // once. Be sure to do this before muteIfNeeded(). | 355 // once. Be sure to do this before muteIfNeeded(). |
| 322 element().removeUserGestureRequirement(); | 356 // Also note that, at this point, we know that we're goint to start |
| 357 // playback. However, we still don't record the metric here. Instead, |
| 358 // we let autoplayMediaEncountered() do that later. |
| 359 element().removeUserGestureRequirement(metric); |
| 360 |
| 361 // Don't bother to call autoplayMediaEncountered, since whoever initiates |
| 362 // playback has do it anyway, in case we don't allow autoplay. |
| 323 | 363 |
| 324 unregisterForPositionUpdatesIfNeeded(); | 364 unregisterForPositionUpdatesIfNeeded(); |
| 325 muteIfNeeded(); | 365 muteIfNeeded(); |
| 326 | 366 |
| 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. | 367 // Do not actually start playback here. |
| 333 } | 368 } |
| 334 | 369 |
| 335 Document& AutoplayExperimentHelper::document() const | 370 Document& AutoplayExperimentHelper::document() const |
| 336 { | 371 { |
| 337 return element().document(); | 372 return element().document(); |
| 338 } | 373 } |
| 339 | 374 |
| 340 HTMLMediaElement& AutoplayExperimentHelper::element() const | 375 HTMLMediaElement& AutoplayExperimentHelper::element() const |
| 341 { | 376 { |
| (...skipping 15 matching lines...) Expand all Loading... |
| 357 if (mode.contains("-ifmuted")) | 392 if (mode.contains("-ifmuted")) |
| 358 value |= IfMuted; | 393 value |= IfMuted; |
| 359 if (mode.contains("-ifmobile")) | 394 if (mode.contains("-ifmobile")) |
| 360 value |= IfMobile; | 395 value |= IfMobile; |
| 361 if (mode.contains("-playmuted")) | 396 if (mode.contains("-playmuted")) |
| 362 value |= PlayMuted; | 397 value |= PlayMuted; |
| 363 | 398 |
| 364 return value; | 399 return value; |
| 365 } | 400 } |
| 366 | 401 |
| 402 void AutoplayExperimentHelper::autoplayMediaEncountered() |
| 403 { |
| 404 if (!m_autoplayMediaCounted) { |
| 405 m_autoplayMediaCounted = true; |
| 406 recordAutoplayMetric(AutoplayMediaFound); |
| 407 |
| 408 // If no user gesture was required, then assume that playback will |
| 409 // actually start. |
| 410 if (!isUserGestureRequiredForPlay()) { |
| 411 m_initialPlayWithoutUserGesture = true; |
| 412 recordAutoplayMetric(m_autoplayDeferredMetric); |
| 413 } |
| 414 } |
| 367 } | 415 } |
| 416 |
| 417 void AutoplayExperimentHelper::initialPlayWithUserGesture() |
| 418 { |
| 419 m_initialPlayWithoutUserGesture = false; |
| 420 } |
| 421 |
| 422 bool AutoplayExperimentHelper::isUserGestureRequiredForPlay() const |
| 423 { |
| 424 return element().isUserGestureRequiredForPlay(); |
| 425 } |
| 426 |
| 427 void AutoplayExperimentHelper::recordMetricsBeforePause() |
| 428 { |
| 429 ASSERT(!element().paused()); |
| 430 |
| 431 const bool bailout = isBailout(); |
| 432 |
| 433 // Record that play was paused. We don't care if it was autoplay, |
| 434 // play(), or the user manually started it. |
| 435 recordAutoplayMetric(AnyPlaybackPaused); |
| 436 if (bailout) |
| 437 recordAutoplayMetric(AnyPlaybackBailout); |
| 438 |
| 439 // If this was a gestureless play, then record that separately. |
| 440 // These cover attr and play() gestureless starts. |
| 441 if (m_initialPlayWithoutUserGesture) { |
| 442 m_initialPlayWithoutUserGesture = false; |
| 443 |
| 444 recordAutoplayMetric(AutoplayPaused); |
| 445 |
| 446 if (bailout) |
| 447 recordAutoplayMetric(AutoplayBailout); |
| 448 } |
| 449 } |
| 450 |
| 451 void AutoplayExperimentHelper::playbackStarted() |
| 452 { |
| 453 recordAutoplayMetric(AnyPlaybackStarted); |
| 454 } |
| 455 |
| 456 void AutoplayExperimentHelper::playbackEnded() |
| 457 { |
| 458 recordAutoplayMetric(AnyPlaybackComplete); |
| 459 if (m_initialPlayWithoutUserGesture) { |
| 460 m_initialPlayWithoutUserGesture = false; |
| 461 recordAutoplayMetric(AutoplayComplete); |
| 462 } |
| 463 } |
| 464 |
| 465 void AutoplayExperimentHelper::recordAutoplayMetric(AutoplayMetrics metric) |
| 466 { |
| 467 element().recordAutoplayMetric(metric); |
| 468 } |
| 469 |
| 470 bool AutoplayExperimentHelper::isBailout() const |
| 471 { |
| 472 // We count the user as having bailed-out on the video if they watched |
| 473 // less than one minute and less than 50% of it. |
| 474 const double playedTime = element().currentTime(); |
| 475 const double progress = playedTime / element().duration(); |
| 476 return (playedTime < 60) && (progress < 0.5); |
| 477 } |
| 478 |
| 479 void AutoplayExperimentHelper::recordSandboxFailure() |
| 480 { |
| 481 // We record autoplayMediaEncountered here because we know |
| 482 // that the autoplay attempt will fail. |
| 483 autoplayMediaEncountered(); |
| 484 recordAutoplayMetric(AutoplayDisabledBySandbox); |
| 485 } |
| 486 |
| 487 void AutoplayExperimentHelper::loadingStarted() |
| 488 { |
| 489 if (m_recordedElement) |
| 490 return; |
| 491 |
| 492 m_recordedElement = true; |
| 493 recordAutoplayMetric(element().isHTMLVideoElement() |
| 494 ? AnyVideoElement : AnyAudioElement); |
| 495 } |
| 496 |
| 497 void AutoplayExperimentHelper::setGestureRemovalReason(AutoplayMetrics deferredM
etric) |
| 498 { |
| 499 m_autoplayDeferredMetric = deferredMetric; |
| 500 } |
| 501 |
| 502 } |
| OLD | NEW |