blob: c63b9982702ac51f1b7a0695bfada53886f88780 [file] [log] [blame]
// Copyright (c) 2011 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/media_internals.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/test_message_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "media/base/media_log.h"
#include "media/base/watch_time_keys.h"
#include "media/blink/watch_time_reporter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
namespace {
const int kTestComponentID = 0;
const char kTestDeviceID[] = "test-device-id";
const char kTestOrigin[] = "https://test.google.com/";
// This class encapsulates a MediaInternals reference. It also has some useful
// methods to receive a callback, deserialize its associated data and expect
// integer/string values.
class MediaInternalsTestBase {
public:
MediaInternalsTestBase()
: media_internals_(content::MediaInternals::GetInstance()) {}
virtual ~MediaInternalsTestBase() {}
protected:
// Extracts and deserializes the JSON update data; merges into |update_data_|.
void UpdateCallbackImpl(const base::string16& update) {
// Each update string looks like "<JavaScript Function Name>({<JSON>});"
// or for video capabilities: "<JavaScript Function Name>([{<JSON>}]);".
// In the second case we will be able to extract the dictionary if it is the
// only member of the list.
// To use the JSON reader we need to strip out the JS function name and ().
std::string utf8_update = base::UTF16ToUTF8(update);
const std::string::size_type first_brace = utf8_update.find('{');
const std::string::size_type last_brace = utf8_update.rfind('}');
std::unique_ptr<base::Value> output_value = base::JSONReader::Read(
utf8_update.substr(first_brace, last_brace - first_brace + 1));
CHECK(output_value);
base::DictionaryValue* output_dict = NULL;
CHECK(output_value->GetAsDictionary(&output_dict));
update_data_.MergeDictionary(output_dict);
}
void ExpectInt(const std::string& key, int expected_value) const {
int actual_value = 0;
ASSERT_TRUE(update_data_.GetInteger(key, &actual_value));
EXPECT_EQ(expected_value, actual_value);
}
void ExpectString(const std::string& key,
const std::string& expected_value) const {
std::string actual_value;
ASSERT_TRUE(update_data_.GetString(key, &actual_value));
EXPECT_EQ(expected_value, actual_value);
}
void ExpectStatus(const std::string& expected_value) const {
ExpectString("status", expected_value);
}
void ExpectListOfStrings(const std::string& key,
const base::ListValue& expected_list) const {
const base::ListValue* actual_list;
ASSERT_TRUE(update_data_.GetList(key, &actual_list));
const size_t expected_size = expected_list.GetSize();
const size_t actual_size = actual_list->GetSize();
ASSERT_EQ(expected_size, actual_size);
for (size_t i = 0; i < expected_size; ++i) {
std::string expected_value, actual_value;
ASSERT_TRUE(expected_list.GetString(i, &expected_value));
ASSERT_TRUE(actual_list->GetString(i, &actual_value));
EXPECT_EQ(expected_value, actual_value);
}
}
const content::TestBrowserThreadBundle thread_bundle_;
base::DictionaryValue update_data_;
content::MediaInternals* const media_internals_;
};
} // namespace
namespace content {
class MediaInternalsVideoCaptureDeviceTest : public testing::Test,
public MediaInternalsTestBase {
public:
MediaInternalsVideoCaptureDeviceTest()
: update_cb_(base::Bind(
&MediaInternalsVideoCaptureDeviceTest::UpdateCallbackImpl,
base::Unretained(this))) {
media_internals_->AddUpdateCallback(update_cb_);
}
~MediaInternalsVideoCaptureDeviceTest() override {
media_internals_->RemoveUpdateCallback(update_cb_);
}
protected:
MediaInternals::UpdateCallback update_cb_;
};
// TODO(chfremer): Consider removing this. This test seem be
// a duplicate implementation of the functionality under test.
// https://crbug.com/630694
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
defined(OS_ANDROID)
TEST_F(MediaInternalsVideoCaptureDeviceTest,
AllCaptureApiTypesHaveProperStringRepresentation) {
using VideoCaptureApi = media::VideoCaptureApi;
std::map<VideoCaptureApi, std::string> api_to_string_map;
api_to_string_map[VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE] = "V4L2 SPLANE";
api_to_string_map[VideoCaptureApi::WIN_MEDIA_FOUNDATION] = "Media Foundation";
api_to_string_map[VideoCaptureApi::WIN_DIRECT_SHOW] = "Direct Show";
api_to_string_map[VideoCaptureApi::MACOSX_AVFOUNDATION] = "AV Foundation";
api_to_string_map[VideoCaptureApi::MACOSX_DECKLINK] = "DeckLink";
api_to_string_map[VideoCaptureApi::ANDROID_API1] = "Camera API1";
api_to_string_map[VideoCaptureApi::ANDROID_API2_LEGACY] =
"Camera API2 Legacy";
api_to_string_map[VideoCaptureApi::ANDROID_API2_FULL] = "Camera API2 Full";
api_to_string_map[VideoCaptureApi::ANDROID_API2_LIMITED] =
"Camera API2 Limited";
api_to_string_map[VideoCaptureApi::ANDROID_TANGO] = "Tango API";
EXPECT_EQ(static_cast<size_t>(VideoCaptureApi::UNKNOWN),
api_to_string_map.size());
for (const auto& map_entry : api_to_string_map) {
media::VideoCaptureDeviceDescriptor descriptor;
descriptor.capture_api = map_entry.first;
EXPECT_EQ(map_entry.second, descriptor.GetCaptureApiTypeString());
}
}
#endif
TEST_F(MediaInternalsVideoCaptureDeviceTest,
VideoCaptureFormatStringIsInExpectedFormat) {
// Since media internals will send video capture capabilities to JavaScript in
// an expected format and there are no public methods for accessing the
// resolutions, frame rates or pixel formats, this test checks that the format
// has not changed. If the test fails because of the changed format, it should
// be updated at the same time as the media internals JS files.
const float kFrameRate = 30.0f;
const gfx::Size kFrameSize(1280, 720);
const media::VideoPixelFormat kPixelFormat = media::PIXEL_FORMAT_I420;
const media::VideoPixelStorage kPixelStorage = media::PIXEL_STORAGE_CPU;
const media::VideoCaptureFormat capture_format(kFrameSize, kFrameRate,
kPixelFormat, kPixelStorage);
const std::string expected_string = base::StringPrintf(
"(%s)@%.3ffps, pixel format: %s, storage: %s",
kFrameSize.ToString().c_str(), kFrameRate,
media::VideoPixelFormatToString(kPixelFormat).c_str(),
media::VideoCaptureFormat::PixelStorageToString(kPixelStorage).c_str());
EXPECT_EQ(expected_string,
media::VideoCaptureFormat::ToString(capture_format));
}
TEST_F(MediaInternalsVideoCaptureDeviceTest,
NotifyVideoCaptureDeviceCapabilitiesEnumerated) {
const int kWidth = 1280;
const int kHeight = 720;
const float kFrameRate = 30.0f;
const media::VideoPixelFormat kPixelFormat = media::PIXEL_FORMAT_I420;
const media::VideoCaptureFormat format_hd({kWidth, kHeight}, kFrameRate,
kPixelFormat);
media::VideoCaptureFormats formats{};
formats.push_back(format_hd);
media::VideoCaptureDeviceDescriptor descriptor;
descriptor.device_id = "dummy";
descriptor.display_name = "dummy";
#if defined(OS_MACOSX)
descriptor.capture_api = media::VideoCaptureApi::MACOSX_AVFOUNDATION;
#elif defined(OS_WIN)
descriptor.capture_api = media::VideoCaptureApi::WIN_DIRECT_SHOW;
#elif defined(OS_LINUX)
descriptor.device_id = "/dev/dummy";
descriptor.capture_api = media::VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE;
#elif defined(OS_ANDROID)
descriptor.capture_api = media::VideoCaptureApi::ANDROID_API2_LEGACY;
#endif
std::vector<std::tuple<media::VideoCaptureDeviceDescriptor,
media::VideoCaptureFormats>>
descriptors_and_formats{};
descriptors_and_formats.push_back(std::make_tuple(descriptor, formats));
// When updating video capture capabilities, the update will serialize
// a JSON array of objects to string. So here, the |UpdateCallbackImpl| will
// deserialize the first object in the array. This means we have to have
// exactly one device_info in the |descriptors_and_formats|.
media_internals_->UpdateVideoCaptureDeviceCapabilities(
descriptors_and_formats);
#if defined(OS_LINUX)
ExpectString("id", "/dev/dummy");
#else
ExpectString("id", "dummy");
#endif
ExpectString("name", "dummy");
base::ListValue expected_list;
expected_list.AppendString(media::VideoCaptureFormat::ToString(format_hd));
ExpectListOfStrings("formats", expected_list);
#if defined(OS_LINUX)
ExpectString("captureApi", "V4L2 SPLANE");
#elif defined(OS_WIN)
ExpectString("captureApi", "Direct Show");
#elif defined(OS_MACOSX)
ExpectString("captureApi", "AV Foundation");
#elif defined(OS_ANDROID)
ExpectString("captureApi", "Camera API2 Legacy");
#endif
}
class MediaInternalsAudioLogTest
: public MediaInternalsTestBase,
public testing::TestWithParam<media::AudioLogFactory::AudioComponent> {
public:
MediaInternalsAudioLogTest()
: update_cb_(base::Bind(&MediaInternalsAudioLogTest::UpdateCallbackImpl,
base::Unretained(this))),
test_params_(MakeAudioParams()),
test_component_(GetParam()),
audio_log_(media_internals_->CreateAudioLog(test_component_)) {
media_internals_->AddUpdateCallback(update_cb_);
}
virtual ~MediaInternalsAudioLogTest() {
media_internals_->RemoveUpdateCallback(update_cb_);
}
protected:
MediaInternals::UpdateCallback update_cb_;
const media::AudioParameters test_params_;
const media::AudioLogFactory::AudioComponent test_component_;
std::unique_ptr<media::AudioLog> audio_log_;
private:
static media::AudioParameters MakeAudioParams() {
media::AudioParameters params(media::AudioParameters::AUDIO_PCM_LINEAR,
media::CHANNEL_LAYOUT_MONO, 48000, 16, 128);
params.set_effects(media::AudioParameters::ECHO_CANCELLER |
media::AudioParameters::DUCKING);
return params;
}
};
TEST_P(MediaInternalsAudioLogTest, AudioLogCreateStartStopErrorClose) {
audio_log_->OnCreated(kTestComponentID, test_params_, kTestDeviceID);
base::RunLoop().RunUntilIdle();
ExpectString("channel_layout",
media::ChannelLayoutToString(test_params_.channel_layout()));
ExpectInt("sample_rate", test_params_.sample_rate());
ExpectInt("frames_per_buffer", test_params_.frames_per_buffer());
ExpectInt("channels", test_params_.channels());
ExpectString("effects", "ECHO_CANCELLER | DUCKING");
ExpectString("device_id", kTestDeviceID);
ExpectInt("component_id", kTestComponentID);
ExpectInt("component_type", test_component_);
ExpectStatus("created");
// Verify OnStarted().
audio_log_->OnStarted(kTestComponentID);
base::RunLoop().RunUntilIdle();
ExpectStatus("started");
// Verify OnStopped().
audio_log_->OnStopped(kTestComponentID);
base::RunLoop().RunUntilIdle();
ExpectStatus("stopped");
// Verify OnError().
const char kErrorKey[] = "error_occurred";
std::string no_value;
ASSERT_FALSE(update_data_.GetString(kErrorKey, &no_value));
audio_log_->OnError(kTestComponentID);
base::RunLoop().RunUntilIdle();
ExpectString(kErrorKey, "true");
// Verify OnClosed().
audio_log_->OnClosed(kTestComponentID);
base::RunLoop().RunUntilIdle();
ExpectStatus("closed");
}
TEST_P(MediaInternalsAudioLogTest, AudioLogCreateClose) {
audio_log_->OnCreated(kTestComponentID, test_params_, kTestDeviceID);
base::RunLoop().RunUntilIdle();
ExpectStatus("created");
audio_log_->OnClosed(kTestComponentID);
base::RunLoop().RunUntilIdle();
ExpectStatus("closed");
}
INSTANTIATE_TEST_CASE_P(
MediaInternalsAudioLogTest,
MediaInternalsAudioLogTest,
testing::Values(media::AudioLogFactory::AUDIO_INPUT_CONTROLLER,
media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER,
media::AudioLogFactory::AUDIO_OUTPUT_STREAM));
class DirectMediaLog : public media::MediaLog {
public:
explicit DirectMediaLog(int render_process_id)
: render_process_id_(render_process_id),
internals_(content::MediaInternals::GetInstance()) {}
~DirectMediaLog() override {}
void AddEvent(std::unique_ptr<media::MediaLogEvent> event) override {
std::vector<media::MediaLogEvent> events(1, *event);
internals_->OnMediaEvents(render_process_id_, events);
}
private:
const int render_process_id_;
MediaInternals* const internals_;
DISALLOW_COPY_AND_ASSIGN(DirectMediaLog);
};
class MediaInternalsWatchTimeTest : public testing::Test,
public MediaInternalsTestBase {
public:
MediaInternalsWatchTimeTest()
: render_process_id_(0),
internals_(content::MediaInternals::GetInstance()),
media_log_(new DirectMediaLog(render_process_id_)),
histogram_tester_(new base::HistogramTester()),
test_recorder_(new ukm::TestUkmRecorder()),
watch_time_keys_(media::GetWatchTimeKeys()),
watch_time_power_keys_(media::GetWatchTimePowerKeys()),
mtbr_keys_({media::kMeanTimeBetweenRebuffersAudioSrc,
media::kMeanTimeBetweenRebuffersAudioMse,
media::kMeanTimeBetweenRebuffersAudioEme,
media::kMeanTimeBetweenRebuffersAudioVideoSrc,
media::kMeanTimeBetweenRebuffersAudioVideoMse,
media::kMeanTimeBetweenRebuffersAudioVideoEme}),
smooth_keys_({media::kRebuffersCountAudioSrc,
media::kRebuffersCountAudioMse,
media::kRebuffersCountAudioEme,
media::kRebuffersCountAudioVideoSrc,
media::kRebuffersCountAudioVideoMse,
media::kRebuffersCountAudioVideoEme}) {
media_log_->AddEvent(media_log_->CreateCreatedEvent(kTestOrigin));
}
void Initialize(bool has_audio,
bool has_video,
bool is_mse,
bool is_encrypted) {
wtr_.reset(new media::WatchTimeReporter(
has_audio, has_video, is_mse, is_encrypted, true, media_log_.get(),
gfx::Size(800, 600),
base::Bind(&MediaInternalsWatchTimeTest::GetCurrentMediaTime,
base::Unretained(this))));
wtr_->set_reporting_interval_for_testing();
}
void CycleWatchTimeReporter() {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
}
void ExpectWatchTime(const std::vector<base::StringPiece>& keys,
base::TimeDelta value) {
for (auto key : watch_time_keys_) {
auto it = std::find(keys.begin(), keys.end(), key);
if (it == keys.end()) {
histogram_tester_->ExpectTotalCount(key.as_string(), 0);
} else {
histogram_tester_->ExpectUniqueSample(key.as_string(),
value.InMilliseconds(), 1);
}
}
}
void ExpectHelper(const std::vector<base::StringPiece>& full_key_list,
const std::vector<base::StringPiece>& keys,
int64_t value) {
for (auto key : full_key_list) {
auto it = std::find(keys.begin(), keys.end(), key);
if (it == keys.end())
histogram_tester_->ExpectTotalCount(key.as_string(), 0);
else
histogram_tester_->ExpectUniqueSample(key.as_string(), value, 1);
}
}
void ExpectMtbrTime(const std::vector<base::StringPiece>& keys,
base::TimeDelta value) {
ExpectHelper(mtbr_keys_, keys, value.InMilliseconds());
}
void ExpectZeroRebuffers(const std::vector<base::StringPiece>& keys) {
ExpectHelper(smooth_keys_, keys, 0);
}
void ExpectRebuffers(const std::vector<base::StringPiece>& keys, int count) {
ExpectHelper(smooth_keys_, keys, count);
}
void ExpectUkmWatchTime(size_t entry, size_t size, base::TimeDelta value) {
ASSERT_LT(entry, test_recorder_->entries_count());
const auto& metrics_vector = test_recorder_->GetEntry(entry)->metrics;
ASSERT_EQ(size, metrics_vector.size());
for (auto& sample : metrics_vector)
EXPECT_EQ(value.InMilliseconds(), sample->value);
}
void ResetHistogramTester() {
histogram_tester_.reset(new base::HistogramTester());
}
MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta());
protected:
const int render_process_id_;
MediaInternals* const internals_;
std::unique_ptr<DirectMediaLog> media_log_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<ukm::TestUkmRecorder> test_recorder_;
std::unique_ptr<media::WatchTimeReporter> wtr_;
const base::flat_set<base::StringPiece> watch_time_keys_;
const base::flat_set<base::StringPiece> watch_time_power_keys_;
const std::vector<base::StringPiece> mtbr_keys_;
const std::vector<base::StringPiece> smooth_keys_;
DISALLOW_COPY_AND_ASSIGN(MediaInternalsWatchTimeTest);
};
TEST_F(MediaInternalsWatchTimeTest, BasicAudio) {
constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, false, true, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
wtr_->OnUnderflow();
wtr_->OnUnderflow();
CycleWatchTimeReporter();
wtr_.reset();
ExpectWatchTime({media::kWatchTimeAudioAll, media::kWatchTimeAudioMse,
media::kWatchTimeAudioEme, media::kWatchTimeAudioAc,
media::kWatchTimeAudioNativeControlsOff,
media::kWatchTimeAudioEmbeddedExperience},
kWatchTimeLate);
ExpectMtbrTime({media::kMeanTimeBetweenRebuffersAudioMse,
media::kMeanTimeBetweenRebuffersAudioEme},
kWatchTimeLate / 2);
ExpectRebuffers(
{media::kRebuffersCountAudioMse, media::kRebuffersCountAudioEme}, 2);
ASSERT_EQ(1U, test_recorder_->sources_count());
ExpectUkmWatchTime(0, 5, kWatchTimeLate);
EXPECT_TRUE(test_recorder_->GetSourceForUrl(kTestOrigin));
}
TEST_F(MediaInternalsWatchTimeTest, BasicVideo) {
constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, true, false, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
wtr_->OnUnderflow();
wtr_->OnUnderflow();
CycleWatchTimeReporter();
wtr_.reset();
ExpectWatchTime(
{media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoDisplayInline,
media::kWatchTimeAudioVideoEmbeddedExperience},
kWatchTimeLate);
ExpectMtbrTime({media::kMeanTimeBetweenRebuffersAudioVideoSrc,
media::kMeanTimeBetweenRebuffersAudioVideoEme},
kWatchTimeLate / 2);
ExpectRebuffers({media::kRebuffersCountAudioVideoSrc,
media::kRebuffersCountAudioVideoEme},
2);
ASSERT_EQ(1U, test_recorder_->sources_count());
ExpectUkmWatchTime(0, 6, kWatchTimeLate);
EXPECT_TRUE(test_recorder_->GetSourceForUrl(kTestOrigin));
}
TEST_F(MediaInternalsWatchTimeTest, BasicPower) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(10);
constexpr base::TimeDelta kWatchTime3 = base::TimeDelta::FromSeconds(30);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillRepeatedly(testing::Return(kWatchTime3));
Initialize(true, true, false, true);
wtr_->OnPlaying();
wtr_->set_is_on_battery_power_for_testing(true);
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Transition back to AC power, this should generate AC watch time as well.
wtr_->OnPowerStateChangeForTesting(false);
CycleWatchTimeReporter();
// This should finalize the power watch time on battery.
ExpectWatchTime({media::kWatchTimeAudioVideoBattery}, kWatchTime2);
ResetHistogramTester();
wtr_.reset();
std::vector<base::StringPiece> normal_keys = {
media::kWatchTimeAudioVideoAll,
media::kWatchTimeAudioVideoSrc,
media::kWatchTimeAudioVideoEme,
media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoDisplayInline,
media::kWatchTimeAudioVideoEmbeddedExperience};
for (auto key : watch_time_keys_) {
if (key == media::kWatchTimeAudioVideoAc) {
histogram_tester_->ExpectUniqueSample(
key.as_string(), (kWatchTime3 - kWatchTime2).InMilliseconds(), 1);
continue;
}
auto it = std::find(normal_keys.begin(), normal_keys.end(), key);
if (it == normal_keys.end()) {
histogram_tester_->ExpectTotalCount(key.as_string(), 0);
} else {
histogram_tester_->ExpectUniqueSample(key.as_string(),
kWatchTime3.InMilliseconds(), 1);
}
}
// Each finalize creates a new source and entry. We don't check the URL here
// since the TestUkmService() helpers DCHECK() a unique URL per source.
ASSERT_EQ(2U, test_recorder_->sources_count());
ASSERT_EQ(2U, test_recorder_->entries_count());
ExpectUkmWatchTime(0, 1, kWatchTime2);
ExpectZeroRebuffers({media::kRebuffersCountAudioVideoSrc,
media::kRebuffersCountAudioVideoEme});
// Verify Media.WatchTime keys are properly stripped for UKM reporting.
EXPECT_TRUE(test_recorder_->FindMetric(test_recorder_->GetEntry(0),
"AudioVideo.Battery"));
// Spot check one of the non-AC keys; this relies on the assumption that the
// AC metric is not last.
const auto& metrics_vector = test_recorder_->GetEntry(1)->metrics;
ASSERT_EQ(6U, metrics_vector.size());
}
TEST_F(MediaInternalsWatchTimeTest, BasicControls) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(10);
constexpr base::TimeDelta kWatchTime3 = base::TimeDelta::FromSeconds(30);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillRepeatedly(testing::Return(kWatchTime3));
Initialize(true, true, false, true);
wtr_->OnNativeControlsEnabled();
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Transition back to non-native controls, this should generate controls watch
// time as well.
wtr_->OnNativeControlsDisabled();
CycleWatchTimeReporter();
// This should finalize the power watch time on native controls.
ExpectWatchTime({media::kWatchTimeAudioVideoNativeControlsOn}, kWatchTime2);
ResetHistogramTester();
wtr_.reset();
std::vector<base::StringPiece> normal_keys = {
media::kWatchTimeAudioVideoAll,
media::kWatchTimeAudioVideoSrc,
media::kWatchTimeAudioVideoEme,
media::kWatchTimeAudioVideoAc,
media::kWatchTimeAudioVideoDisplayInline,
media::kWatchTimeAudioVideoEmbeddedExperience};
for (auto key : watch_time_keys_) {
if (key == media::kWatchTimeAudioVideoNativeControlsOff) {
histogram_tester_->ExpectUniqueSample(
key.as_string(), (kWatchTime3 - kWatchTime2).InMilliseconds(), 1);
continue;
}
auto it = std::find(normal_keys.begin(), normal_keys.end(), key);
if (it == normal_keys.end()) {
histogram_tester_->ExpectTotalCount(key.as_string(), 0);
} else {
histogram_tester_->ExpectUniqueSample(key.as_string(),
kWatchTime3.InMilliseconds(), 1);
}
}
// Each finalize creates a new source and entry. We don't check the URL here
// since the TestUkmService() helpers DCHECK() a unique URL per source.
ASSERT_EQ(2U, test_recorder_->sources_count());
ASSERT_EQ(2U, test_recorder_->entries_count());
ExpectUkmWatchTime(0, 1, kWatchTime2);
// Verify Media.WatchTime keys are properly stripped for UKM reporting.
EXPECT_TRUE(test_recorder_->FindMetric(test_recorder_->GetEntry(0),
"AudioVideo.NativeControlsOn"));
// Spot check one of the non-AC keys; this relies on the assumption that the
// AC metric is not last.
const auto& metrics_vector = test_recorder_->GetEntry(1)->metrics;
ASSERT_EQ(6U, metrics_vector.size());
EXPECT_EQ(kWatchTime3.InMilliseconds(), metrics_vector.back()->value);
}
TEST_F(MediaInternalsWatchTimeTest, BasicDisplay) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(10);
constexpr base::TimeDelta kWatchTime3 = base::TimeDelta::FromSeconds(30);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillRepeatedly(testing::Return(kWatchTime3));
Initialize(true, true, false, true);
wtr_->OnDisplayTypeFullscreen();
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Transition back to display inline, this should generate controls watch time
// as well.
wtr_->OnDisplayTypeInline();
CycleWatchTimeReporter();
// This should finalize the power watch time on display type.
ExpectWatchTime({media::kWatchTimeAudioVideoDisplayFullscreen}, kWatchTime2);
ResetHistogramTester();
wtr_.reset();
std::vector<base::StringPiece> normal_keys = {
media::kWatchTimeAudioVideoAll,
media::kWatchTimeAudioVideoSrc,
media::kWatchTimeAudioVideoEme,
media::kWatchTimeAudioVideoAc,
media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoEmbeddedExperience};
for (auto key : watch_time_keys_) {
if (key == media::kWatchTimeAudioVideoDisplayInline) {
histogram_tester_->ExpectUniqueSample(
key.as_string(), (kWatchTime3 - kWatchTime2).InMilliseconds(), 1);
continue;
}
auto it = std::find(normal_keys.begin(), normal_keys.end(), key);
if (it == normal_keys.end()) {
histogram_tester_->ExpectTotalCount(key.as_string(), 0);
} else {
histogram_tester_->ExpectUniqueSample(key.as_string(),
kWatchTime3.InMilliseconds(), 1);
}
}
// Each finalize creates a new source and entry. We don't check the URL here
// since the TestUkmService() helpers DCHECK() a unique URL per source.
ASSERT_EQ(2U, test_recorder_->sources_count());
ASSERT_EQ(2U, test_recorder_->entries_count());
ExpectUkmWatchTime(0, 1, kWatchTime2);
// Verify Media.WatchTime keys are properly stripped for UKM reporting.
EXPECT_TRUE(test_recorder_->FindMetric(test_recorder_->GetEntry(0),
"AudioVideo.DisplayFullscreen"));
// Spot check one of the non-AC keys; this relies on the assumption that the
// AC metric is not last.
const auto& metrics_vector = test_recorder_->GetEntry(1)->metrics;
ASSERT_EQ(6U, metrics_vector.size());
EXPECT_EQ(kWatchTime3.InMilliseconds(), metrics_vector.back()->value);
}
TEST_F(MediaInternalsWatchTimeTest, PowerControlsFinalize) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(10);
constexpr base::TimeDelta kWatchTime3 = base::TimeDelta::FromSeconds(30);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillRepeatedly(testing::Return(kWatchTime3));
Initialize(true, true, false, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Transition controls and power.
wtr_->OnNativeControlsEnabled();
wtr_->OnPowerStateChangeForTesting(true);
CycleWatchTimeReporter();
// This should finalize the power and controls watch times.
ExpectWatchTime({media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoAc},
kWatchTime2);
ResetHistogramTester();
wtr_.reset();
}
TEST_F(MediaInternalsWatchTimeTest, PowerDisplayFinalize) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(10);
constexpr base::TimeDelta kWatchTime3 = base::TimeDelta::FromSeconds(30);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillRepeatedly(testing::Return(kWatchTime3));
Initialize(true, true, false, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Transition display and power.
wtr_->OnDisplayTypePictureInPicture();
wtr_->OnPowerStateChangeForTesting(true);
CycleWatchTimeReporter();
// This should finalize the power and controls watch times.
ExpectWatchTime(
{media::kWatchTimeAudioVideoDisplayInline, media::kWatchTimeAudioVideoAc},
kWatchTime2);
ResetHistogramTester();
wtr_.reset();
}
TEST_F(MediaInternalsWatchTimeTest, PowerDisplayControlsFinalize) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(10);
constexpr base::TimeDelta kWatchTime3 = base::TimeDelta::FromSeconds(30);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime1))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillOnce(testing::Return(kWatchTime2))
.WillRepeatedly(testing::Return(kWatchTime3));
Initialize(true, true, false, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Transition display and power.
wtr_->OnNativeControlsEnabled();
wtr_->OnDisplayTypeFullscreen();
wtr_->OnPowerStateChangeForTesting(true);
CycleWatchTimeReporter();
// This should finalize the power and controls watch times.
ExpectWatchTime({media::kWatchTimeAudioVideoDisplayInline,
media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoAc},
kWatchTime2);
ResetHistogramTester();
wtr_.reset();
}
TEST_F(MediaInternalsWatchTimeTest, BasicHidden) {
constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, true, false, true);
wtr_->OnHidden();
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
wtr_.reset();
ExpectWatchTime({media::kWatchTimeAudioVideoBackgroundAll,
media::kWatchTimeAudioVideoBackgroundSrc,
media::kWatchTimeAudioVideoBackgroundEme,
media::kWatchTimeAudioVideoBackgroundAc,
media::kWatchTimeAudioVideoBackgroundEmbeddedExperience},
kWatchTimeLate);
ASSERT_EQ(1U, test_recorder_->sources_count());
ExpectUkmWatchTime(0, 4, kWatchTimeLate);
EXPECT_TRUE(test_recorder_->GetSourceForUrl(kTestOrigin));
}
TEST_F(MediaInternalsWatchTimeTest, FinalizeWithoutWatchTime) {
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillRepeatedly(testing::Return(base::TimeDelta()));
Initialize(true, true, false, true);
wtr_->OnPlaying();
wtr_.reset();
// No watch time should have been recorded even though a finalize event will
// be sent.
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
ExpectMtbrTime(std::vector<base::StringPiece>(), base::TimeDelta());
ExpectZeroRebuffers(std::vector<base::StringPiece>());
ASSERT_EQ(0U, test_recorder_->sources_count());
}
TEST_F(MediaInternalsWatchTimeTest, PlayerDestructionFinalizes) {
constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, true, true, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
media_log_->AddEvent(
media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
ExpectWatchTime(
{media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoMse,
media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoDisplayInline,
media::kWatchTimeAudioVideoEmbeddedExperience},
kWatchTimeLate);
ExpectZeroRebuffers({media::kRebuffersCountAudioVideoMse,
media::kRebuffersCountAudioVideoEme});
ASSERT_EQ(1U, test_recorder_->sources_count());
ExpectUkmWatchTime(0, 6, kWatchTimeLate);
EXPECT_TRUE(test_recorder_->GetSourceForUrl(kTestOrigin));
}
TEST_F(MediaInternalsWatchTimeTest, ProcessDestructionFinalizes) {
constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, true, false, true);
wtr_->OnPlaying();
// No log should have been generated yet since the message loop has not had
// any chance to pump.
CycleWatchTimeReporter();
ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
CycleWatchTimeReporter();
// Also verify that if UKM has already been destructed, we don't crash.
test_recorder_.reset();
internals_->OnProcessTerminatedForTesting(render_process_id_);
ExpectWatchTime(
{media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
media::kWatchTimeAudioVideoNativeControlsOff,
media::kWatchTimeAudioVideoDisplayInline,
media::kWatchTimeAudioVideoEmbeddedExperience},
kWatchTimeLate);
ExpectZeroRebuffers({media::kRebuffersCountAudioVideoSrc,
media::kRebuffersCountAudioVideoEme});
}
} // namespace content