blob: 6fc004d2fe3589af076dceddddbc8aafa1854c49 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/html/AutoplayUmaHelper.h"
#include "core/dom/Document.h"
#include "core/dom/ElementVisibilityObserver.h"
#include "core/events/Event.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLMediaElement.h"
#include "platform/Histogram.h"
#include "wtf/CurrentTime.h"
namespace blink {
namespace {
const int32_t maxOffscreenDurationUmaMS = 60 * 60 * 1000;
const int32_t offscreenDurationUmaBucketCount = 50;
} // namespace
AutoplayUmaHelper* AutoplayUmaHelper::create(HTMLMediaElement* element) {
return new AutoplayUmaHelper(element);
}
AutoplayUmaHelper::AutoplayUmaHelper(HTMLMediaElement* element)
: EventListener(CPPEventListenerType),
m_source(AutoplaySource::NumberOfSources),
m_element(element),
m_mutedVideoPlayMethodVisibilityObserver(nullptr),
m_mutedVideoAutoplayOffscreenStartTimeMS(0),
m_mutedVideoAutoplayOffscreenDurationMS(0),
m_isVisible(false),
m_mutedVideoOffscreenDurationVisibilityObserver(nullptr) {}
AutoplayUmaHelper::~AutoplayUmaHelper() = default;
bool AutoplayUmaHelper::operator==(const EventListener& other) const {
return this == &other;
}
void AutoplayUmaHelper::onAutoplayInitiated(AutoplaySource source) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, videoHistogram,
("Media.Video.Autoplay",
static_cast<int>(AutoplaySource::NumberOfSources)));
DEFINE_STATIC_LOCAL(EnumerationHistogram, mutedVideoHistogram,
("Media.Video.Autoplay.Muted",
static_cast<int>(AutoplaySource::NumberOfSources)));
DEFINE_STATIC_LOCAL(EnumerationHistogram, audioHistogram,
("Media.Audio.Autoplay",
static_cast<int>(AutoplaySource::NumberOfSources)));
DEFINE_STATIC_LOCAL(
EnumerationHistogram, blockedMutedVideoHistogram,
("Media.Video.Autoplay.Muted.Blocked", AutoplayBlockedReasonMax));
// Autoplay already initiated
// TODO(zqzhang): how about having autoplay attribute and calling `play()` in
// the script?
if (m_source != AutoplaySource::NumberOfSources)
return;
m_source = source;
// Record the source.
if (m_element->isHTMLVideoElement()) {
videoHistogram.count(static_cast<int>(m_source));
if (m_element->muted())
mutedVideoHistogram.count(static_cast<int>(m_source));
} else {
audioHistogram.count(static_cast<int>(m_source));
}
// Record if it will be blocked by Data Saver or Autoplay setting.
if (m_element->isHTMLVideoElement() && m_element->muted() &&
RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) {
bool dataSaverEnabled =
m_element->document().settings() &&
m_element->document().settings()->dataSaverEnabled();
bool blockedBySetting = !m_element->isAutoplayAllowedPerSettings();
if (dataSaverEnabled && blockedBySetting) {
blockedMutedVideoHistogram.count(
AutoplayBlockedReasonDataSaverAndSetting);
} else if (dataSaverEnabled) {
blockedMutedVideoHistogram.count(AutoplayBlockedReasonDataSaver);
} else if (blockedBySetting) {
blockedMutedVideoHistogram.count(AutoplayBlockedReasonSetting);
}
}
m_element->addEventListener(EventTypeNames::playing, this, false);
}
void AutoplayUmaHelper::recordAutoplayUnmuteStatus(
AutoplayUnmuteActionStatus status) {
DEFINE_STATIC_LOCAL(
EnumerationHistogram, autoplayUnmuteHistogram,
("Media.Video.Autoplay.Muted.UnmuteAction",
static_cast<int>(AutoplayUnmuteActionStatus::NumberOfStatus)));
autoplayUnmuteHistogram.count(static_cast<int>(status));
}
void AutoplayUmaHelper::didMoveToNewDocument(Document& oldDocument) {
if (!shouldListenToUnloadEvent())
return;
if (oldDocument.domWindow())
oldDocument.domWindow()->removeEventListener(EventTypeNames::unload, this,
false);
if (m_element->document().domWindow())
m_element->document().domWindow()->addEventListener(EventTypeNames::unload,
this, false);
}
void AutoplayUmaHelper::onVisibilityChangedForMutedVideoPlayMethodBecomeVisible(
bool isVisible) {
if (!isVisible || !m_mutedVideoPlayMethodVisibilityObserver)
return;
maybeStopRecordingMutedVideoPlayMethodBecomeVisible(true);
}
void AutoplayUmaHelper::onVisibilityChangedForMutedVideoOffscreenDuration(
bool isVisible) {
if (isVisible == m_isVisible)
return;
if (isVisible)
m_mutedVideoAutoplayOffscreenDurationMS +=
static_cast<int64_t>(monotonicallyIncreasingTimeMS()) -
m_mutedVideoAutoplayOffscreenStartTimeMS;
else
m_mutedVideoAutoplayOffscreenStartTimeMS =
static_cast<int64_t>(monotonicallyIncreasingTimeMS());
m_isVisible = isVisible;
}
void AutoplayUmaHelper::handleEvent(ExecutionContext* executionContext,
Event* event) {
if (event->type() == EventTypeNames::playing)
handlePlayingEvent();
else if (event->type() == EventTypeNames::pause)
handlePauseEvent();
else if (event->type() == EventTypeNames::unload)
handleUnloadEvent();
else
NOTREACHED();
}
void AutoplayUmaHelper::handlePlayingEvent() {
maybeStartRecordingMutedVideoPlayMethodBecomeVisible();
maybeStartRecordingMutedVideoOffscreenDuration();
m_element->removeEventListener(EventTypeNames::playing, this, false);
}
void AutoplayUmaHelper::handlePauseEvent() {
maybeStopRecordingMutedVideoOffscreenDuration();
}
void AutoplayUmaHelper::handleUnloadEvent() {
maybeStopRecordingMutedVideoPlayMethodBecomeVisible(false);
maybeStopRecordingMutedVideoOffscreenDuration();
}
void AutoplayUmaHelper::maybeStartRecordingMutedVideoPlayMethodBecomeVisible() {
if (m_source != AutoplaySource::Method || !m_element->isHTMLVideoElement() ||
!m_element->muted())
return;
m_mutedVideoPlayMethodVisibilityObserver = new ElementVisibilityObserver(
m_element,
WTF::bind(&AutoplayUmaHelper::
onVisibilityChangedForMutedVideoPlayMethodBecomeVisible,
wrapWeakPersistent(this)));
m_mutedVideoPlayMethodVisibilityObserver->start();
if (m_element->document().domWindow())
m_element->document().domWindow()->addEventListener(EventTypeNames::unload,
this, false);
}
void AutoplayUmaHelper::maybeStopRecordingMutedVideoPlayMethodBecomeVisible(
bool visible) {
if (!m_mutedVideoPlayMethodVisibilityObserver)
return;
DEFINE_STATIC_LOCAL(BooleanHistogram, histogram,
("Media.Video.Autoplay.Muted.PlayMethod.BecomesVisible"));
histogram.count(visible);
m_mutedVideoPlayMethodVisibilityObserver->stop();
m_mutedVideoPlayMethodVisibilityObserver = nullptr;
maybeUnregisterUnloadListener();
}
void AutoplayUmaHelper::maybeStartRecordingMutedVideoOffscreenDuration() {
if (!m_element->isHTMLVideoElement() || !m_element->muted())
return;
// Start recording muted video playing offscreen duration.
m_mutedVideoAutoplayOffscreenStartTimeMS =
static_cast<int64_t>(monotonicallyIncreasingTimeMS());
m_isVisible = false;
m_mutedVideoOffscreenDurationVisibilityObserver =
new ElementVisibilityObserver(
m_element,
WTF::bind(&AutoplayUmaHelper::
onVisibilityChangedForMutedVideoOffscreenDuration,
wrapWeakPersistent(this)));
m_mutedVideoOffscreenDurationVisibilityObserver->start();
m_element->addEventListener(EventTypeNames::pause, this, false);
if (m_element->document().domWindow())
m_element->document().domWindow()->addEventListener(EventTypeNames::unload,
this, false);
}
void AutoplayUmaHelper::maybeStopRecordingMutedVideoOffscreenDuration() {
if (!m_mutedVideoOffscreenDurationVisibilityObserver)
return;
if (!m_isVisible)
m_mutedVideoAutoplayOffscreenDurationMS +=
static_cast<int64_t>(monotonicallyIncreasingTimeMS()) -
m_mutedVideoAutoplayOffscreenStartTimeMS;
// Since histograms uses int32_t, the duration needs to be limited to
// std::numeric_limits<int32_t>::max().
int32_t boundedTime = static_cast<int32_t>(
std::min<int64_t>(m_mutedVideoAutoplayOffscreenDurationMS,
std::numeric_limits<int32_t>::max()));
if (m_source == AutoplaySource::Attribute) {
DEFINE_STATIC_LOCAL(
CustomCountHistogram, durationHistogram,
("Media.Video.Autoplay.Muted.Attribute.OffscreenDuration", 1,
maxOffscreenDurationUmaMS, offscreenDurationUmaBucketCount));
durationHistogram.count(boundedTime);
} else {
DEFINE_STATIC_LOCAL(
CustomCountHistogram, durationHistogram,
("Media.Video.Autoplay.Muted.PlayMethod.OffscreenDuration", 1,
maxOffscreenDurationUmaMS, offscreenDurationUmaBucketCount));
durationHistogram.count(boundedTime);
}
m_mutedVideoOffscreenDurationVisibilityObserver->stop();
m_mutedVideoOffscreenDurationVisibilityObserver = nullptr;
m_mutedVideoAutoplayOffscreenDurationMS = 0;
m_element->removeEventListener(EventTypeNames::pause, this, false);
maybeUnregisterUnloadListener();
}
void AutoplayUmaHelper::maybeUnregisterUnloadListener() {
if (!shouldListenToUnloadEvent() && m_element->document().domWindow())
m_element->document().domWindow()->removeEventListener(
EventTypeNames::unload, this, false);
}
bool AutoplayUmaHelper::shouldListenToUnloadEvent() const {
return m_mutedVideoPlayMethodVisibilityObserver ||
m_mutedVideoOffscreenDurationVisibilityObserver;
}
DEFINE_TRACE(AutoplayUmaHelper) {
EventListener::trace(visitor);
visitor->trace(m_element);
visitor->trace(m_mutedVideoPlayMethodVisibilityObserver);
visitor->trace(m_mutedVideoOffscreenDurationVisibilityObserver);
}
} // namespace blink