summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/browser/media/audible_metrics.cc79
-rw-r--r--content/browser/media/audible_metrics.h49
-rw-r--r--content/browser/media/audible_metrics_unittest.cc397
-rw-r--r--content/browser/media/audio_stream_monitor.cc16
-rw-r--r--content/browser/media/audio_stream_monitor.h8
-rw-r--r--content/browser/media/audio_stream_monitor_unittest.cc87
-rw-r--r--content/browser/media/media_web_contents_observer.cc27
-rw-r--r--content/browser/media/media_web_contents_observer.h6
-rw-r--r--content/browser/web_contents/web_contents_impl.cc3
-rw-r--r--content/content_browser.gypi2
-rw-r--r--content/content_tests.gypi1
11 files changed, 647 insertions, 28 deletions
diff --git a/content/browser/media/audible_metrics.cc b/content/browser/media/audible_metrics.cc
new file mode 100644
index 0000000..4a74028
--- /dev/null
+++ b/content/browser/media/audible_metrics.cc
@@ -0,0 +1,79 @@
+// 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 "content/browser/media/audible_metrics.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics.h"
+#include "base/time/default_tick_clock.h"
+
+namespace content {
+
+AudibleMetrics::AudibleMetrics()
+ : max_concurrent_audible_web_contents_in_session_(0),
+ clock_(new base::DefaultTickClock()) {
+}
+
+AudibleMetrics::~AudibleMetrics() {
+}
+
+void AudibleMetrics::UpdateAudibleWebContentsState(
+ const WebContents* web_contents, bool audible) {
+ bool found =
+ audible_web_contents_.find(web_contents) != audible_web_contents_.end();
+ if (found == audible)
+ return;
+
+ if (audible)
+ AddAudibleWebContents(web_contents);
+ else
+ RemoveAudibleWebContents(web_contents);
+}
+
+void AudibleMetrics::SetClockForTest(scoped_ptr<base::TickClock> test_clock) {
+ clock_ = std::move(test_clock);
+}
+
+void AudibleMetrics::AddAudibleWebContents(const WebContents* web_contents) {
+ base::RecordAction(base::UserMetricsAction("Media.Audible.AddTab"));
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Media.Audible.ConcurrentTabsWhenStarting", audible_web_contents_.size(),
+ 1, 10, 11);
+
+ audible_web_contents_.insert(web_contents);
+ if (audible_web_contents_.size() > 1 &&
+ concurrent_web_contents_start_time_.is_null()) {
+ concurrent_web_contents_start_time_ = clock_->NowTicks();
+ }
+
+ if (audible_web_contents_.size() >
+ max_concurrent_audible_web_contents_in_session_) {
+ max_concurrent_audible_web_contents_in_session_ =
+ audible_web_contents_.size();
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Media.Audible.MaxConcurrentTabsInSession",
+ max_concurrent_audible_web_contents_in_session_,
+ 1, 10, 11);
+ }
+}
+
+void AudibleMetrics::RemoveAudibleWebContents(const WebContents* web_contents) {
+ base::RecordAction(base::UserMetricsAction("Media.Audible.RemoveTab"));
+
+ audible_web_contents_.erase(web_contents);
+
+ if (audible_web_contents_.size() <= 1 &&
+ !concurrent_web_contents_start_time_.is_null()) {
+ base::TimeDelta concurrent_total_time =
+ clock_->NowTicks() - concurrent_web_contents_start_time_;
+ concurrent_web_contents_start_time_ = base::TimeTicks();
+
+ UMA_HISTOGRAM_LONG_TIMES("Media.Audible.ConcurrentTabsTime",
+ concurrent_total_time);
+ }
+}
+
+} // namespace content
diff --git a/content/browser/media/audible_metrics.h b/content/browser/media/audible_metrics.h
new file mode 100644
index 0000000..408bc8c
--- /dev/null
+++ b/content/browser/media/audible_metrics.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_AUDIBLE_METRICS_H_
+#define CONTENT_BROWSER_MEDIA_AUDIBLE_METRICS_H_
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/tick_clock.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class WebContents;
+
+// This class handles metrics regarding audible WebContents.
+// It does register three different information:
+// - how many WebContents are audible when a WebContents become audible.
+// - how long multiple WebContents are audible at the same time.
+// - for a browsing session, how often and how many WebContents get audible at
+// the same time.
+class CONTENT_EXPORT AudibleMetrics {
+ public:
+ AudibleMetrics();
+ ~AudibleMetrics();
+
+ void UpdateAudibleWebContentsState(const WebContents* web_contents,
+ bool audible);
+
+ void SetClockForTest(scoped_ptr<base::TickClock> test_clock);
+
+ private:
+ void AddAudibleWebContents(const WebContents* web_contents);
+ void RemoveAudibleWebContents(const WebContents* web_contents);
+
+ base::TimeTicks concurrent_web_contents_start_time_;
+ size_t max_concurrent_audible_web_contents_in_session_;
+ scoped_ptr<base::TickClock> clock_;
+
+ std::set<const WebContents*> audible_web_contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudibleMetrics);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_AUDIBLE_METRICS_H_
diff --git a/content/browser/media/audible_metrics_unittest.cc b/content/browser/media/audible_metrics_unittest.cc
new file mode 100644
index 0000000..1a6216b6
--- /dev/null
+++ b/content/browser/media/audible_metrics_unittest.cc
@@ -0,0 +1,397 @@
+// 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 "content/browser/media/audible_metrics.h"
+
+#include "base/metrics/histogram_samples.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/user_action_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+static const WebContents* WEB_CONTENTS_0 = reinterpret_cast<WebContents*>(0x00);
+static const WebContents* WEB_CONTENTS_1 = reinterpret_cast<WebContents*>(0x01);
+static const WebContents* WEB_CONTENTS_2 = reinterpret_cast<WebContents*>(0x10);
+static const WebContents* WEB_CONTENTS_3 = reinterpret_cast<WebContents*>(0x11);
+
+static const char* CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM =
+ "Media.Audible.ConcurrentTabsWhenStarting";
+static const char* MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM =
+ "Media.Audible.MaxConcurrentTabsInSession";
+static const char* CONCURRENT_TABS_TIME_HISTOGRAM =
+ "Media.Audible.ConcurrentTabsTime";
+
+static const char* ADD_TAB_USER_ACTION = "Media.Audible.AddTab";
+static const char* REMOVE_TAB_USER_ACTION = "Media.Audible.RemoveTab";
+
+class AudibleMetricsTest : public testing::Test {
+ public:
+ AudibleMetricsTest() = default;
+
+ void SetUp() override {
+ clock_ = new base::SimpleTestTickClock();
+ // Set the clock to a value different than 0 so the time it gives is
+ // recognized as initialized.
+ clock_->Advance(base::TimeDelta::FromMilliseconds(1));
+ audible_metrics_.SetClockForTest(
+ scoped_ptr<base::SimpleTestTickClock>(clock_));
+ }
+
+ void TearDown() override {
+ clock_ = nullptr;
+ }
+
+ base::SimpleTestTickClock* clock() { return clock_; }
+
+ AudibleMetrics* audible_metrics() {
+ return &audible_metrics_;
+ };
+
+ const base::UserActionTester& user_action_tester() const {
+ return user_action_tester_;
+ }
+
+ scoped_ptr<base::HistogramSamples> GetHistogramSamplesSinceTestStart(
+ const std::string& name) {
+ return histogram_tester_.GetHistogramSamplesSinceCreation(name);
+ }
+
+ private:
+ base::SimpleTestTickClock* clock_ = nullptr;
+ AudibleMetrics audible_metrics_;
+ base::HistogramTester histogram_tester_;
+ base::UserActionTester user_action_tester_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudibleMetricsTest);
+};
+
+} // anonymous namespace
+
+TEST_F(AudibleMetricsTest, CreateAndKillDoesNothing) {
+ {
+ scoped_ptr<AudibleMetrics> audible_metrics(new AudibleMetrics());
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(0, samples->TotalCount());
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(0, samples->TotalCount());
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(0, samples->TotalCount());
+ }
+
+ EXPECT_EQ(0, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, AudibleStart) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(0));
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1));
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(0, samples->TotalCount());
+ }
+
+ EXPECT_EQ(1, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, AudibleStartAndStop) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(0));
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1));
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(0, samples->TotalCount());
+ }
+
+ EXPECT_EQ(1, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(1, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, AddSameTabIsNoOp) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(0));
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1));
+ }
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(0, samples->TotalCount());
+ }
+
+ EXPECT_EQ(1, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, RemoveUnknownTabIsNoOp) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+
+ EXPECT_EQ(0, GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM)->TotalCount());
+ EXPECT_EQ(0, GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM)->TotalCount());
+ EXPECT_EQ(0, GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TABS_TIME_HISTOGRAM)->TotalCount());
+
+ EXPECT_EQ(0, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, ConcurrentTabsInSessionIsIncremental) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_3, true);
+
+ scoped_ptr<base::HistogramSamples> samples(GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(4, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1));
+ EXPECT_EQ(1, samples->GetCount(2));
+ EXPECT_EQ(1, samples->GetCount(3));
+ EXPECT_EQ(1, samples->GetCount(4));
+
+ EXPECT_EQ(4, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, ConcurrentTabsInSessionKeepTrackOfRemovedTabs) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_3, true);
+
+ scoped_ptr<base::HistogramSamples> samples(GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(2, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1));
+ EXPECT_EQ(1, samples->GetCount(2));
+
+ EXPECT_EQ(4, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(3, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, ConcurrentTabsInSessionIsNotCountedTwice) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_3, true);
+
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_3, false);
+
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_3, true);
+
+ scoped_ptr<base::HistogramSamples> samples(GetHistogramSamplesSinceTestStart(
+ MAX_CONCURRENT_TAB_IN_SESSION_HISTOGRAM));
+ EXPECT_EQ(4, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1));
+ EXPECT_EQ(1, samples->GetCount(2));
+ EXPECT_EQ(1, samples->GetCount(3));
+ EXPECT_EQ(1, samples->GetCount(4));
+
+ EXPECT_EQ(8, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(4, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, ConcurrentTabsWhenStartingAddedPerTab) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(2, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(0));
+ EXPECT_EQ(1, samples->GetCount(1));
+ }
+
+ EXPECT_EQ(2, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+
+ // Added again: ignored.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(2, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(0));
+ EXPECT_EQ(1, samples->GetCount(1));
+ }
+
+ EXPECT_EQ(2, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(0, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+
+ // Removing both.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, false);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(2, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(0));
+ EXPECT_EQ(1, samples->GetCount(1));
+ }
+
+ EXPECT_EQ(2, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(2, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+
+ // Adding them after removed, it is counted.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TAB_WHEN_STARTING_HISTOGRAM));
+ EXPECT_EQ(4, samples->TotalCount());
+ EXPECT_EQ(2, samples->GetCount(0));
+ EXPECT_EQ(2, samples->GetCount(1));
+ }
+
+ EXPECT_EQ(4, user_action_tester().GetActionCount(ADD_TAB_USER_ACTION));
+ EXPECT_EQ(2, user_action_tester().GetActionCount(REMOVE_TAB_USER_ACTION));
+}
+
+TEST_F(AudibleMetricsTest, ConcurrentTabsTimeRequiresTwoAudibleTabs) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+
+ clock()->Advance(base::TimeDelta::FromMilliseconds(1000));
+
+ // No record because concurrent audible tabs still running.
+ EXPECT_EQ(0, GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TABS_TIME_HISTOGRAM)->TotalCount());
+
+ // No longer concurrent.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1000));
+ }
+
+ // Stopping the second tab is a no-op.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, false);
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1000));
+ }
+}
+
+TEST_F(AudibleMetricsTest, ConcurrentTabsTimeRunsAsLongAsTwoAudibleTabs) {
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, true);
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, true);
+
+ clock()->Advance(base::TimeDelta::FromMilliseconds(1000));
+
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_2, true);
+
+ clock()->Advance(base::TimeDelta::FromMilliseconds(500));
+
+ // Mutes one of the three audible tabs.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_1, false);
+
+ // No record because concurrent audible tabs still running.
+ EXPECT_EQ(0, GetHistogramSamplesSinceTestStart(
+ CONCURRENT_TABS_TIME_HISTOGRAM)->TotalCount());
+
+ // Mutes the first audible tab.
+ audible_metrics()->UpdateAudibleWebContentsState(WEB_CONTENTS_0, false);
+ {
+ scoped_ptr<base::HistogramSamples> samples(
+ GetHistogramSamplesSinceTestStart(CONCURRENT_TABS_TIME_HISTOGRAM));
+ EXPECT_EQ(1, samples->TotalCount());
+ EXPECT_EQ(1, samples->GetCount(1500));
+ }
+}
+
+} // namespace content
diff --git a/content/browser/media/audio_stream_monitor.cc b/content/browser/media/audio_stream_monitor.cc
index 2f6a7e4..b19e82ff 100644
--- a/content/browser/media/audio_stream_monitor.cc
+++ b/content/browser/media/audio_stream_monitor.cc
@@ -30,7 +30,8 @@ AudioStreamMonitor* AudioStreamMonitorFromRenderFrame(int render_process_id,
AudioStreamMonitor::AudioStreamMonitor(WebContents* contents)
: web_contents_(contents),
clock_(&default_tick_clock_),
- was_recently_audible_(false)
+ was_recently_audible_(false),
+ is_audible_(false)
{
DCHECK(web_contents_);
}
@@ -42,6 +43,11 @@ bool AudioStreamMonitor::WasRecentlyAudible() const {
return was_recently_audible_;
}
+bool AudioStreamMonitor::IsCurrentlyAudible() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return is_audible_;
+}
+
// static
void AudioStreamMonitor::StartMonitoringStream(
int render_process_id,
@@ -124,6 +130,9 @@ void AudioStreamMonitor::StopMonitoringStreamOnUIThread(int render_process_id,
}
void AudioStreamMonitor::Poll() {
+ bool was_audible = is_audible_;
+ is_audible_ = false;
+
for (StreamPollCallbackMap::const_iterator it = poll_callbacks_.begin();
it != poll_callbacks_.end();
++it) {
@@ -132,12 +141,17 @@ void AudioStreamMonitor::Poll() {
// information except for "is it audible?"
const float power_dbfs = it->second.Run().first;
const float kSilenceThresholdDBFS = -72.24719896f;
+
if (power_dbfs >= kSilenceThresholdDBFS) {
last_blurt_time_ = clock_->NowTicks();
+ is_audible_ = true;
MaybeToggle();
break; // No need to poll remaining streams.
}
}
+
+ if (is_audible_ != was_audible)
+ web_contents_->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
}
void AudioStreamMonitor::MaybeToggle() {
diff --git a/content/browser/media/audio_stream_monitor.h b/content/browser/media/audio_stream_monitor.h
index 0eb5147..12f0605 100644
--- a/content/browser/media/audio_stream_monitor.h
+++ b/content/browser/media/audio_stream_monitor.h
@@ -52,6 +52,11 @@ class CONTENT_EXPORT AudioStreamMonitor {
// the killing of tabs making sounds).
bool WasRecentlyAudible() const;
+ // Returns true if the audio is currently audible from the given WebContents.
+ // The difference from WasRecentlyAudible() is that this method will return
+ // false as soon as the WebContents stop producing sound.
+ bool IsCurrentlyAudible() const;
+
// Starts or stops audio level monitoring respectively for the stream owned by
// the specified renderer. Safe to call from any thread.
//
@@ -139,6 +144,9 @@ class CONTENT_EXPORT AudioStreamMonitor {
// should be turned on.
bool was_recently_audible_;
+ // Whether the WebContents is currently audible.
+ bool is_audible_;
+
// Calls Poll() at regular intervals while |poll_callbacks_| is non-empty.
base::RepeatingTimer poll_timer_;
diff --git a/content/browser/media/audio_stream_monitor_unittest.cc b/content/browser/media/audio_stream_monitor_unittest.cc
index d9999bf..9ecee0a 100644
--- a/content/browser/media/audio_stream_monitor_unittest.cc
+++ b/content/browser/media/audio_stream_monitor_unittest.cc
@@ -93,16 +93,37 @@ class AudioStreamMonitorTest : public RenderViewHostTestHarness {
monitor_->off_timer_.IsRunning());
}
- void ExpectWebContentsWillBeNotifiedOnce(bool should_be_audible) {
+ void ExpectIsCurrentlyAudible() const {
+ EXPECT_TRUE(monitor_->IsCurrentlyAudible());
+ }
+
+ void ExpectNotCurrentlyAudible() const {
+ EXPECT_FALSE(monitor_->IsCurrentlyAudible());
+ }
+
+ void ExpectRecentlyAudibleChangeNotification(bool new_recently_audible) {
+ EXPECT_CALL(
+ mock_web_contents_delegate_,
+ NavigationStateChanged(RenderViewHostTestHarness::web_contents(),
+ INVALIDATE_TYPE_TAB))
+ .WillOnce(InvokeWithoutArgs(
+ this,
+ new_recently_audible
+ ? &AudioStreamMonitorTest::ExpectWasRecentlyAudible
+ : &AudioStreamMonitorTest::ExpectNotRecentlyAudible))
+ .RetiresOnSaturation();
+ }
+
+ void ExpectCurrentlyAudibleChangeNotification(bool new_audible) {
EXPECT_CALL(
mock_web_contents_delegate_,
NavigationStateChanged(RenderViewHostTestHarness::web_contents(),
INVALIDATE_TYPE_TAB))
.WillOnce(InvokeWithoutArgs(
this,
- should_be_audible
- ? &AudioStreamMonitorTest::ExpectIsNotifyingForToggleOn
- : &AudioStreamMonitorTest::ExpectIsNotifyingForToggleOff))
+ new_audible
+ ? &AudioStreamMonitorTest::ExpectIsCurrentlyAudible
+ : &AudioStreamMonitorTest::ExpectNotCurrentlyAudible))
.RetiresOnSaturation();
}
@@ -136,11 +157,11 @@ class AudioStreamMonitorTest : public RenderViewHostTestHarness {
return std::make_pair(current_power_[stream_id], false);
}
- void ExpectIsNotifyingForToggleOn() {
+ void ExpectWasRecentlyAudible() const {
EXPECT_TRUE(monitor_->WasRecentlyAudible());
}
- void ExpectIsNotifyingForToggleOff() {
+ void ExpectNotRecentlyAudible() const {
EXPECT_FALSE(monitor_->WasRecentlyAudible());
}
@@ -155,14 +176,17 @@ class AudioStreamMonitorTest : public RenderViewHostTestHarness {
// ReadPowerAndClipCallback, and is not polling at other times.
TEST_F(AudioStreamMonitorTest, PollsWhenProvidedACallback) {
EXPECT_FALSE(monitor_->WasRecentlyAudible());
+ ExpectNotCurrentlyAudible();
ExpectIsPolling(kRenderProcessId, kStreamId, false);
StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId));
EXPECT_FALSE(monitor_->WasRecentlyAudible());
+ ExpectNotCurrentlyAudible();
ExpectIsPolling(kRenderProcessId, kStreamId, true);
StopMonitoring(kRenderProcessId, kStreamId);
EXPECT_FALSE(monitor_->WasRecentlyAudible());
+ ExpectNotCurrentlyAudible();
ExpectIsPolling(kRenderProcessId, kStreamId, false);
}
@@ -175,13 +199,16 @@ TEST_F(AudioStreamMonitorTest,
// Expect WebContents will get one call form AudioStreamMonitor to toggle the
// indicator on upon the very first poll.
- ExpectWebContentsWillBeNotifiedOnce(true);
+ ExpectRecentlyAudibleChangeNotification(true);
// Loop, each time testing a slightly longer period of polled silence. The
- // indicator should remain on throughout.
- int num_silence_polls = 0;
+ // recently audible state should not change while the currently audible one
+ // should.
+ int num_silence_polls = 1;
base::TimeTicks last_blurt_time;
do {
+ ExpectCurrentlyAudibleChangeNotification(true);
+
// Poll an audible signal, and expect tab indicator state is on.
SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power());
last_blurt_time = GetTestClockTime();
@@ -189,6 +216,8 @@ TEST_F(AudioStreamMonitorTest,
ExpectTabWasRecentlyAudible(true, last_blurt_time);
AdvanceClock(one_polling_interval());
+ ExpectCurrentlyAudibleChangeNotification(false);
+
// Poll a silent signal repeatedly, ensuring that the indicator is being
// held on during the holding period.
SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power());
@@ -207,7 +236,7 @@ TEST_F(AudioStreamMonitorTest,
// At this point, the clock has just advanced to beyond the holding period, so
// the next firing of the off timer should turn off the tab indicator. Also,
// make sure it stays off for several cycles thereafter.
- ExpectWebContentsWillBeNotifiedOnce(false);
+ ExpectRecentlyAudibleChangeNotification(false);
for (int i = 0; i < 10; ++i) {
SimulateOffTimerFired();
ExpectTabWasRecentlyAudible(false, last_blurt_time);
@@ -224,15 +253,19 @@ TEST_F(AudioStreamMonitorTest, HandlesMultipleStreamsBlurting) {
base::TimeTicks last_blurt_time;
ExpectTabWasRecentlyAudible(false, last_blurt_time);
+ ExpectNotCurrentlyAudible();
// Measure audible sound from the first stream and silence from the second.
- // The indicator turns on (i.e., tab was recently audible).
- ExpectWebContentsWillBeNotifiedOnce(true);
+ // The tab becomes audible.
+ ExpectRecentlyAudibleChangeNotification(true);
+ ExpectCurrentlyAudibleChangeNotification(true);
+
SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power());
SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power());
last_blurt_time = GetTestClockTime();
SimulatePollTimerFired();
ExpectTabWasRecentlyAudible(true, last_blurt_time);
+ ExpectIsCurrentlyAudible();
// Halfway through the holding period, the second stream joins in. The
// indicator stays on.
@@ -242,39 +275,52 @@ TEST_F(AudioStreamMonitorTest, HandlesMultipleStreamsBlurting) {
last_blurt_time = GetTestClockTime();
SimulatePollTimerFired(); // Restarts holding period.
ExpectTabWasRecentlyAudible(true, last_blurt_time);
+ ExpectIsCurrentlyAudible();
// Now, measure silence from both streams. After an entire holding period
- // has passed (since the second stream joined in), the indicator should turn
- // off.
- ExpectWebContentsWillBeNotifiedOnce(false);
+ // has passed (since the second stream joined in), the tab will no longer
+ // become audible nor recently audible.
+ ExpectCurrentlyAudibleChangeNotification(false);
+ ExpectRecentlyAudibleChangeNotification(false);
+
AdvanceClock(holding_period());
SimulateOffTimerFired();
SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power());
SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power());
SimulatePollTimerFired();
ExpectTabWasRecentlyAudible(false, last_blurt_time);
+ ExpectNotCurrentlyAudible();
// Now, measure silence from the first stream and audible sound from the
- // second. The indicator turns back on.
- ExpectWebContentsWillBeNotifiedOnce(true);
+ // second. The tab becomes audible again.
+ ExpectRecentlyAudibleChangeNotification(true);
+ ExpectCurrentlyAudibleChangeNotification(true);
+
SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::max_power());
last_blurt_time = GetTestClockTime();
SimulatePollTimerFired();
ExpectTabWasRecentlyAudible(true, last_blurt_time);
+ ExpectIsCurrentlyAudible();
// From here onwards, both streams are silent. Halfway through the holding
- // period, the indicator should not have changed.
+ // period, the tab is no longer audible but stays as recently audible.
+ ExpectCurrentlyAudibleChangeNotification(false);
+
SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power());
AdvanceClock(holding_period() / 2);
SimulatePollTimerFired();
SimulateOffTimerFired();
ExpectTabWasRecentlyAudible(true, last_blurt_time);
+ ExpectNotCurrentlyAudible();
+
+ // Just past the holding period, the tab is no longer marked as recently
+ // audible.
+ ExpectRecentlyAudibleChangeNotification(false);
- // Just past the holding period, the indicator should be turned off.
- ExpectWebContentsWillBeNotifiedOnce(false);
AdvanceClock(holding_period() - (GetTestClockTime() - last_blurt_time));
SimulateOffTimerFired();
ExpectTabWasRecentlyAudible(false, last_blurt_time);
+ ExpectNotCurrentlyAudible();
// Polling should not turn the indicator back while both streams are remaining
// silent.
@@ -282,6 +328,7 @@ TEST_F(AudioStreamMonitorTest, HandlesMultipleStreamsBlurting) {
AdvanceClock(one_polling_interval());
SimulatePollTimerFired();
ExpectTabWasRecentlyAudible(false, last_blurt_time);
+ ExpectNotCurrentlyAudible();
}
}
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index 0591e25..67ef036 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -4,8 +4,11 @@
#include "content/browser/media/media_web_contents_observer.h"
+#include "base/lazy_instance.h"
#include "base/memory/scoped_ptr.h"
#include "build/build_config.h"
+#include "content/browser/media/audible_metrics.h"
+#include "content/browser/media/audio_stream_monitor.h"
#include "content/browser/power_save_blocker_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame_messages.h"
@@ -15,23 +18,43 @@
namespace content {
+namespace {
+
+static base::LazyInstance<AudibleMetrics>::Leaky g_audible_metrics =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // anonymous namespace
+
MediaWebContentsObserver::MediaWebContentsObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
MediaWebContentsObserver::~MediaWebContentsObserver() {}
+void MediaWebContentsObserver::WebContentsDestroyed() {
+ g_audible_metrics.Get().UpdateAudibleWebContentsState(web_contents(), false);
+}
+
void MediaWebContentsObserver::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
ClearPowerSaveBlockers(render_frame_host);
}
-void MediaWebContentsObserver::MaybeUpdateAudibleState(bool recently_audible) {
- if (recently_audible) {
+void MediaWebContentsObserver::MaybeUpdateAudibleState() {
+ if (!AudioStreamMonitor::monitoring_available())
+ return;
+
+ AudioStreamMonitor* audio_stream_monitor =
+ static_cast<WebContentsImpl*>(web_contents())->audio_stream_monitor();
+
+ if (audio_stream_monitor->WasRecentlyAudible()) {
if (!audio_power_save_blocker_)
CreateAudioPowerSaveBlocker();
} else {
audio_power_save_blocker_.reset();
}
+
+ g_audible_metrics.Get().UpdateAudibleWebContentsState(
+ web_contents(), audio_stream_monitor->IsCurrentlyAudible());
}
bool MediaWebContentsObserver::OnMessageReceived(
diff --git a/content/browser/media/media_web_contents_observer.h b/content/browser/media/media_web_contents_observer.h
index 6752256..4429d93 100644
--- a/content/browser/media/media_web_contents_observer.h
+++ b/content/browser/media/media_web_contents_observer.h
@@ -28,11 +28,11 @@ class CONTENT_EXPORT MediaWebContentsObserver : public WebContentsObserver {
explicit MediaWebContentsObserver(WebContents* web_contents);
~MediaWebContentsObserver() override;
- // Called when the audible state has changed. If inaudible any audio power
- // save blockers are released.
- void MaybeUpdateAudibleState(bool recently_audible);
+ // Called by WebContentsImpl when the audible state may have changed.
+ void MaybeUpdateAudibleState();
// WebContentsObserver implementation.
+ void WebContentsDestroyed() override;
void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
bool OnMessageReceived(const IPC::Message& message,
RenderFrameHost* render_frame_host) override;
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 4586f7e..bc23d3e 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1185,8 +1185,7 @@ void WebContentsImpl::NotifyNavigationStateChanged(
"466285 WebContentsImpl::NotifyNavigationStateChanged"));
// Notify the media observer of potential audibility changes.
if (changed_flags & INVALIDATE_TYPE_TAB) {
- media_web_contents_observer_->MaybeUpdateAudibleState(
- AudioStreamMonitor::monitoring_available() && WasRecentlyAudible());
+ media_web_contents_observer_->MaybeUpdateAudibleState();
}
if (delegate_)
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index bc4ed21..4369cda 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -1002,6 +1002,8 @@
'browser/media/android/provision_fetcher_impl.h',
'browser/media/android/url_provision_fetcher.cc',
'browser/media/android/url_provision_fetcher.h',
+ 'browser/media/audible_metrics.cc',
+ 'browser/media/audible_metrics.h',
'browser/media/audio_stream_monitor.cc',
'browser/media/audio_stream_monitor.h',
'browser/media/capture/audio_mirroring_manager.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 69ab363..9730388 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -504,6 +504,7 @@
'browser/loader/upload_data_stream_builder_unittest.cc',
'browser/mach_broker_mac_unittest.cc',
'browser/media/android/media_session_uma_helper_unittest.cc',
+ 'browser/media/audible_metrics_unittest.cc',
'browser/media/audio_stream_monitor_unittest.cc',
'browser/media/capture/audio_mirroring_manager_unittest.cc',
'browser/media/capture/cursor_renderer_aura_unittest.cc',