blob: e1d160299bb90ceb875954d1fbfb5d6ac633f43f [file] [log] [blame]
// Copyright 2017 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 "media/mojo/services/watch_time_recorder.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/hash/hash.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_message_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/ukm/test_ukm_recorder.h"
#include "media/base/watch_time_keys.h"
#include "media/mojo/services/media_metrics_provider.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using UkmEntry = ukm::builders::Media_BasicPlayback;
namespace media {
constexpr char kTestOrigin[] = "https://test.google.com/";
class WatchTimeRecorderTest : public testing::Test {
public:
WatchTimeRecorderTest()
: computation_keys_(
{WatchTimeKey::kAudioSrc, WatchTimeKey::kAudioMse,
WatchTimeKey::kAudioEme, WatchTimeKey::kAudioVideoSrc,
WatchTimeKey::kAudioVideoMse, WatchTimeKey::kAudioVideoEme}),
mtbr_keys_({kMeanTimeBetweenRebuffersAudioSrc,
kMeanTimeBetweenRebuffersAudioMse,
kMeanTimeBetweenRebuffersAudioEme,
kMeanTimeBetweenRebuffersAudioVideoSrc,
kMeanTimeBetweenRebuffersAudioVideoMse,
kMeanTimeBetweenRebuffersAudioVideoEme}),
smooth_keys_({kRebuffersCountAudioSrc, kRebuffersCountAudioMse,
kRebuffersCountAudioEme, kRebuffersCountAudioVideoSrc,
kRebuffersCountAudioVideoMse,
kRebuffersCountAudioVideoEme}),
discard_keys_({kDiscardedWatchTimeAudioSrc, kDiscardedWatchTimeAudioMse,
kDiscardedWatchTimeAudioEme,
kDiscardedWatchTimeAudioVideoSrc,
kDiscardedWatchTimeAudioVideoMse,
kDiscardedWatchTimeAudioVideoEme}) {
source_id_ = test_recorder_->GetNewSourceID();
ResetMetricRecorders();
MediaMetricsProvider::Create(
MediaMetricsProvider::BrowsingMode::kIncognito,
MediaMetricsProvider::FrameStatus::kTopFrame,
base::BindRepeating(&WatchTimeRecorderTest::GetSourceId,
base::Unretained(this)),
base::BindRepeating(
[]() { return learning::FeatureValue(0); }) /* origin callback */,
VideoDecodePerfHistory::SaveCallback(), mojo::MakeRequest(&provider_));
}
~WatchTimeRecorderTest() override { base::RunLoop().RunUntilIdle(); }
void Initialize(mojom::PlaybackPropertiesPtr properties) {
provider_->Initialize(properties->is_mse,
properties->is_mse ? mojom::MediaURLScheme::kUnknown
: mojom::MediaURLScheme::kHttp);
provider_->AcquireWatchTimeRecorder(std::move(properties),
mojo::MakeRequest(&wtr_));
}
void Initialize(bool has_audio,
bool has_video,
bool is_mse,
bool is_encrypted) {
Initialize(mojom::PlaybackProperties::New(
has_audio, has_video, false, false, is_mse, is_encrypted, false));
}
void ExpectWatchTime(const std::vector<base::StringPiece>& keys,
base::TimeDelta value) {
for (int i = 0; i <= static_cast<int>(WatchTimeKey::kWatchTimeKeyMax);
++i) {
const base::StringPiece test_key =
ConvertWatchTimeKeyToStringForUma(static_cast<WatchTimeKey>(i));
if (test_key.empty())
continue;
auto it = std::find(keys.begin(), keys.end(), test_key);
if (it == keys.end()) {
histogram_tester_->ExpectTotalCount(test_key.as_string(), 0);
} else {
histogram_tester_->ExpectUniqueSample(test_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 ExpectNoUkmWatchTime() {
// We always add a source in testing.
ASSERT_EQ(1u, test_recorder_->sources_count());
ASSERT_EQ(0u, test_recorder_->entries_count());
}
void ExpectUkmWatchTime(const std::vector<base::StringPiece>& keys,
base::TimeDelta value) {
const auto& entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
for (auto key : keys) {
test_recorder_->ExpectEntryMetric(entry, key.data(),
value.InMilliseconds());
}
}
}
void ResetMetricRecorders() {
histogram_tester_.reset(new base::HistogramTester());
// Ensure cleared global before attempting to create a new TestUkmReporter.
test_recorder_.reset();
test_recorder_.reset(new ukm::TestAutoSetUkmRecorder());
test_recorder_->UpdateSourceURL(source_id_, GURL(kTestOrigin));
}
ukm::SourceId GetSourceId() { return source_id_; }
MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta());
protected:
base::test::TaskEnvironment task_environment_;
mojom::MediaMetricsProviderPtr provider_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_recorder_;
ukm::SourceId source_id_;
mojom::WatchTimeRecorderPtr wtr_;
const std::vector<WatchTimeKey> computation_keys_;
const std::vector<base::StringPiece> mtbr_keys_;
const std::vector<base::StringPiece> smooth_keys_;
const std::vector<base::StringPiece> discard_keys_;
DISALLOW_COPY_AND_ASSIGN(WatchTimeRecorderTest);
};
TEST_F(WatchTimeRecorderTest, TestBasicReporting) {
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(25);
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(50);
for (int i = 0; i <= static_cast<int>(WatchTimeKey::kWatchTimeKeyMax); ++i) {
const WatchTimeKey key = static_cast<WatchTimeKey>(i);
auto key_str = ConvertWatchTimeKeyToStringForUma(key);
SCOPED_TRACE(key_str.empty() ? base::NumberToString(i)
: key_str.as_string());
// Values for |is_background| and |is_muted| don't matter in this test since
// they don't prevent the muted or background keys from being recorded.
Initialize(true, false, true, true);
wtr_->UpdateSecondaryProperties(mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600)));
wtr_->RecordWatchTime(WatchTimeKey::kWatchTimeKeyMax, kWatchTime1);
wtr_->RecordWatchTime(key, kWatchTime1);
wtr_->RecordWatchTime(key, kWatchTime2);
base::RunLoop().RunUntilIdle();
// Nothing should be recorded yet since we haven't finalized.
ExpectWatchTime({}, base::TimeDelta());
// Only the requested key should be finalized.
wtr_->FinalizeWatchTime({key});
base::RunLoop().RunUntilIdle();
if (!key_str.empty())
ExpectWatchTime({key_str}, kWatchTime2);
// These keys are only reported for a full finalize.
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers({});
ExpectNoUkmWatchTime();
// Verify nothing else is recorded except for what we finalized above.
ResetMetricRecorders();
wtr_.reset();
base::RunLoop().RunUntilIdle();
ExpectWatchTime({}, base::TimeDelta());
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers({});
switch (key) {
case WatchTimeKey::kAudioAll:
case WatchTimeKey::kAudioBackgroundAll:
case WatchTimeKey::kAudioVideoAll:
case WatchTimeKey::kAudioVideoBackgroundAll:
case WatchTimeKey::kAudioVideoMutedAll:
case WatchTimeKey::kVideoAll:
case WatchTimeKey::kVideoBackgroundAll:
ExpectUkmWatchTime({UkmEntry::kWatchTimeName}, kWatchTime2);
break;
// These keys are not reported, instead we boolean flags for each type.
case WatchTimeKey::kAudioMse:
case WatchTimeKey::kAudioEme:
case WatchTimeKey::kAudioSrc:
case WatchTimeKey::kAudioEmbeddedExperience:
case WatchTimeKey::kAudioBackgroundMse:
case WatchTimeKey::kAudioBackgroundEme:
case WatchTimeKey::kAudioBackgroundSrc:
case WatchTimeKey::kAudioBackgroundEmbeddedExperience:
case WatchTimeKey::kAudioVideoMse:
case WatchTimeKey::kAudioVideoEme:
case WatchTimeKey::kAudioVideoSrc:
case WatchTimeKey::kAudioVideoEmbeddedExperience:
case WatchTimeKey::kAudioVideoMutedMse:
case WatchTimeKey::kAudioVideoMutedEme:
case WatchTimeKey::kAudioVideoMutedSrc:
case WatchTimeKey::kAudioVideoMutedEmbeddedExperience:
case WatchTimeKey::kAudioVideoBackgroundMse:
case WatchTimeKey::kAudioVideoBackgroundEme:
case WatchTimeKey::kAudioVideoBackgroundSrc:
case WatchTimeKey::kAudioVideoBackgroundEmbeddedExperience:
case WatchTimeKey::kVideoMse:
case WatchTimeKey::kVideoEme:
case WatchTimeKey::kVideoSrc:
case WatchTimeKey::kVideoEmbeddedExperience:
case WatchTimeKey::kVideoBackgroundMse:
case WatchTimeKey::kVideoBackgroundEme:
case WatchTimeKey::kVideoBackgroundSrc:
case WatchTimeKey::kVideoBackgroundEmbeddedExperience:
ExpectUkmWatchTime({}, base::TimeDelta());
break;
// These keys roll up into the battery watch time field.
case WatchTimeKey::kAudioBattery:
case WatchTimeKey::kAudioBackgroundBattery:
case WatchTimeKey::kAudioVideoBattery:
case WatchTimeKey::kAudioVideoMutedBattery:
case WatchTimeKey::kAudioVideoBackgroundBattery:
case WatchTimeKey::kVideoBattery:
case WatchTimeKey::kVideoBackgroundBattery:
ExpectUkmWatchTime({UkmEntry::kWatchTime_BatteryName}, kWatchTime2);
break;
// These keys roll up into the AC watch time field.
case WatchTimeKey::kAudioAc:
case WatchTimeKey::kAudioBackgroundAc:
case WatchTimeKey::kAudioVideoAc:
case WatchTimeKey::kAudioVideoBackgroundAc:
case WatchTimeKey::kAudioVideoMutedAc:
case WatchTimeKey::kVideoAc:
case WatchTimeKey::kVideoBackgroundAc:
ExpectUkmWatchTime({UkmEntry::kWatchTime_ACName}, kWatchTime2);
break;
case WatchTimeKey::kAudioVideoDisplayFullscreen:
case WatchTimeKey::kAudioVideoMutedDisplayFullscreen:
case WatchTimeKey::kVideoDisplayFullscreen:
ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayFullscreenName},
kWatchTime2);
break;
case WatchTimeKey::kAudioVideoDisplayInline:
case WatchTimeKey::kAudioVideoMutedDisplayInline:
case WatchTimeKey::kVideoDisplayInline:
ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayInlineName},
kWatchTime2);
break;
case WatchTimeKey::kAudioVideoDisplayPictureInPicture:
case WatchTimeKey::kAudioVideoMutedDisplayPictureInPicture:
case WatchTimeKey::kVideoDisplayPictureInPicture:
ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayPictureInPictureName},
kWatchTime2);
break;
case WatchTimeKey::kAudioNativeControlsOn:
case WatchTimeKey::kAudioVideoNativeControlsOn:
case WatchTimeKey::kAudioVideoMutedNativeControlsOn:
case WatchTimeKey::kVideoNativeControlsOn:
ExpectUkmWatchTime({UkmEntry::kWatchTime_NativeControlsOnName},
kWatchTime2);
break;
case WatchTimeKey::kAudioNativeControlsOff:
case WatchTimeKey::kAudioVideoNativeControlsOff:
case WatchTimeKey::kAudioVideoMutedNativeControlsOff:
case WatchTimeKey::kVideoNativeControlsOff:
ExpectUkmWatchTime({UkmEntry::kWatchTime_NativeControlsOffName},
kWatchTime2);
break;
}
ResetMetricRecorders();
}
}
TEST_F(WatchTimeRecorderTest, TestRebufferingMetrics) {
Initialize(true, false, true, true);
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(50);
for (auto key : computation_keys_)
wtr_->RecordWatchTime(key, kWatchTime);
wtr_->UpdateUnderflowCount(1);
wtr_->UpdateUnderflowCount(2);
// Trigger finalization of everything.
wtr_->FinalizeWatchTime({});
base::RunLoop().RunUntilIdle();
ExpectMtbrTime(mtbr_keys_, kWatchTime / 2);
ExpectRebuffers(smooth_keys_, 2);
// Now rerun the test without any rebuffering.
ResetMetricRecorders();
for (auto key : computation_keys_)
wtr_->RecordWatchTime(key, kWatchTime);
wtr_->FinalizeWatchTime({});
base::RunLoop().RunUntilIdle();
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers(smooth_keys_);
// Now rerun the test with a small amount of watch time and ensure rebuffering
// isn't recorded because we haven't met the watch time requirements.
ResetMetricRecorders();
constexpr base::TimeDelta kWatchTimeShort = base::TimeDelta::FromSeconds(5);
for (auto key : computation_keys_)
wtr_->RecordWatchTime(key, kWatchTimeShort);
wtr_->UpdateUnderflowCount(1);
wtr_->UpdateUnderflowCount(2);
wtr_->FinalizeWatchTime({});
base::RunLoop().RunUntilIdle();
// Nothing should be logged since this doesn't meet requirements.
ExpectMtbrTime({}, base::TimeDelta());
for (auto key : smooth_keys_)
histogram_tester_->ExpectTotalCount(key.as_string(), 0);
}
TEST_F(WatchTimeRecorderTest, TestDiscardMetrics) {
Initialize(true, false, true, true);
wtr_->UpdateSecondaryProperties(mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600)));
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(5);
for (auto key : computation_keys_)
wtr_->RecordWatchTime(key, kWatchTime);
// Trigger finalization of everything.
wtr_.reset();
base::RunLoop().RunUntilIdle();
// No standard watch time should be recorded because it falls below the
// reporting threshold.
ExpectWatchTime({}, base::TimeDelta());
// Verify the time was instead logged to the discard keys.
for (auto key : discard_keys_) {
histogram_tester_->ExpectUniqueSample(key.as_string(),
kWatchTime.InMilliseconds(), 1);
}
// UKM watch time won't be logged because we aren't sending "All" keys.
ExpectUkmWatchTime({}, base::TimeDelta());
}
#define EXPECT_UKM(name, value) \
test_recorder_->ExpectEntryMetric(entry, name, value)
#define EXPECT_NO_UKM(name) \
EXPECT_FALSE(test_recorder_->EntryHasMetric(entry, name))
#define EXPECT_HAS_UKM(name) \
EXPECT_TRUE(test_recorder_->EntryHasMetric(entry, name));
TEST_F(WatchTimeRecorderTest, TestFinalizeNoDuplication) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, false, false, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
// Verify that UKM is reported along with the watch time.
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(4);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
// Finalize everything. UKM is only recorded at destruction, so this should do
// nothing.
wtr_->FinalizeWatchTime({});
base::RunLoop().RunUntilIdle();
// No watch time should have been recorded since this is below the UMA report
// threshold.
ExpectWatchTime({}, base::TimeDelta());
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers({});
ExpectNoUkmWatchTime();
const auto& empty_entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(0u, empty_entries.size());
// Verify UKM is logged at destruction time.
ResetMetricRecorders();
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
}
}
TEST_F(WatchTimeRecorderTest, FinalizeWithoutWatchTime) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, false, false, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
// Finalize everything. UKM is only recorded at destruction, so this should do
// nothing.
wtr_->FinalizeWatchTime({});
base::RunLoop().RunUntilIdle();
// No watch time should have been recorded even though a finalize event will
// be sent, however a UKM entry with the playback properties will still be
// generated.
ExpectWatchTime({}, base::TimeDelta());
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers({});
ExpectNoUkmWatchTime();
const auto& empty_entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(0u, empty_entries.size());
// Destructing the recorder should generate a UKM report though.
ResetMetricRecorders();
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
EXPECT_NO_UKM(UkmEntry::kWatchTimeName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
}
}
TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideo) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, false, false, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kCenc,
EncryptionMode::kCbcs, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(4);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
}
}
TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoWithExtras) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, true, true, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecOpus, kCodecVP9, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(54);
const base::TimeDelta kWatchTime2 = kWatchTime * 2;
const base::TimeDelta kWatchTime3 = kWatchTime / 3;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime2);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAc, kWatchTime);
// Ensure partial finalize does not affect final report.
wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoAc});
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoBattery, kWatchTime);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoNativeControlsOn, kWatchTime);
wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoNativeControlsOn});
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoNativeControlsOff, kWatchTime);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayFullscreen,
kWatchTime3);
wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayFullscreen});
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayInline, kWatchTime3);
wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayInline});
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
kWatchTime3);
wtr_->UpdateUnderflowCount(3);
wtr_->OnError(PIPELINE_ERROR_DECODE);
secondary_properties->audio_decoder_name = "MojoAudioDecoder";
secondary_properties->video_decoder_name = "MojoVideoDecoder";
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
wtr_->SetAutoplayInitiated(true);
wtr_->OnDurationChanged(base::TimeDelta::FromSeconds(9500));
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_ACName, kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_BatteryName, kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_NativeControlsOnName,
kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_NativeControlsOffName,
kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_DisplayFullscreenName,
kWatchTime3.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_DisplayInlineName,
kWatchTime3.InMilliseconds());
EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
kWatchTime3.InMilliseconds());
EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
kWatchTime2.InMilliseconds() / 3);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
// Values taken from .cc private enumeration (and should never change).
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
// Duration should be rounded up.
EXPECT_UKM(UkmEntry::kDurationName, 10000000);
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 3);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, true);
}
}
TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoBackgroundMuted) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, true, true, false, false, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(54);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoBackgroundAll, kWatchTime);
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_NO_UKM(UkmEntry::kDurationName);
EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
}
}
TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoDuration) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, false, false, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
wtr_->OnDurationChanged(base::TimeDelta::FromSeconds(12345));
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
// Duration should be rounded to the most significant digit.
EXPECT_UKM(UkmEntry::kDurationName, 10000000);
EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
}
}
TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoDurationInfinite) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, false, false, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "", "", EncryptionMode::kUnencrypted,
EncryptionMode::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
wtr_->OnDurationChanged(kInfiniteDuration);
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties->natural_size.height());
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
// Duration should be unrecorded when infinite.
EXPECT_NO_UKM(UkmEntry::kDurationName);
EXPECT_NO_UKM(UkmEntry::kWatchTimeName);
EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
}
}
// Might happen due to timing issues, so ensure no crashes.
TEST_F(WatchTimeRecorderTest, NoSecondaryProperties) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, true, true, false);
Initialize(properties.Clone());
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(54);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(0u, entries.size());
}
TEST_F(WatchTimeRecorderTest, SingleSecondaryPropertiesUnknownToKnown) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, true, true, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
kUnknownAudioCodec, kUnknownVideoCodec, "", "",
EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kWatchTime = base::TimeDelta::FromSeconds(54);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "FFmpegAudioDecoder", "FFmpegVideoDecoder",
EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
wtr_.reset();
base::RunLoop().RunUntilIdle();
// Since we only transitioned unknown values to known values, there should be
// only a single UKM entry.
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties2->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties2->natural_size.height());
EXPECT_NO_UKM(UkmEntry::kDurationName);
}
}
TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, true, true, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
kCodecOpus, kCodecVP9, "MojoAudioDecoder", "MojoVideoDecoder",
EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "FFmpegAudioDecoder", "FFmpegVideoDecoder",
EncryptionMode::kCenc, EncryptionMode::kCenc, gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(25);
const int kUnderflowCount2 = 3;
// Watch time and underflow counts continue to accumulate during property
// changes, so we report the sum here instead of just kWatchTime2.
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll,
kWatchTime1 + kWatchTime2);
wtr_->UpdateUnderflowCount(kUnderflowCount1 + kUnderflowCount2);
wtr_->OnError(PIPELINE_ERROR_DECODE);
wtr_->OnDurationChanged(base::TimeDelta::FromSeconds(5125));
wtr_.reset();
base::RunLoop().RunUntilIdle();
// All records should have the following:
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(2u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_UKM(UkmEntry::kDurationName, 5000000);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
// All records inherit the final pipeline status code.
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
}
// The first record should have...
auto* entry = entries[0];
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
kWatchTime1.InMilliseconds() / kUnderflowCount1);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties1->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties1->natural_size.height());
// The second record should have...
entry = entries[1];
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
kWatchTime2.InMilliseconds() / kUnderflowCount2);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties2->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties2->natural_size.height());
}
TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, true, true, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
kCodecOpus, kCodecVP9, "MojoAudioDecoder", "MojoVideoDecoder",
EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "FFmpegAudioDecoder", "FFmpegVideoDecoder",
EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
// Don't record any watch time to the new record, it should report zero watch
// time upon destruction. This ensures there's always a Finalize to prevent
// UKM was receiving negative values from the previous unfinalized record.
wtr_.reset();
base::RunLoop().RunUntilIdle();
// All records should have the following:
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(2u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_NO_UKM(UkmEntry::kDurationName);
}
// The first record should have...
auto* entry = entries[0];
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
kWatchTime1.InMilliseconds() / kUnderflowCount1);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties1->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties1->natural_size.height());
// The second record should have...
entry = entries[1];
EXPECT_UKM(UkmEntry::kWatchTimeName, 0);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties2->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties2->natural_size.height());
}
TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, true, true, false);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
kCodecOpus, kCodecVP9, "MojoAudioDecoder", "MojoVideoDecoder",
EncryptionMode::kCbcs, EncryptionMode::kCbcs, gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
// Force a finalize here so that the there is no unfinalized watch time at the
// time of the secondary property update.
wtr_->FinalizeWatchTime({});
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, "FFmpegAudioDecoder", "FFmpegVideoDecoder",
EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(25);
const int kUnderflowCount2 = 3;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime2);
wtr_->UpdateUnderflowCount(kUnderflowCount2);
wtr_->OnError(PIPELINE_ERROR_DECODE);
wtr_.reset();
base::RunLoop().RunUntilIdle();
// All records should have the following:
const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(2u, entries.size());
for (const auto* entry : entries) {
test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
EXPECT_UKM(UkmEntry::kIsMSEName, properties->is_mse);
EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
EXPECT_NO_UKM(UkmEntry::kDurationName);
// All records inherit the final pipeline status code.
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
}
// The first record should have...
auto* entry = entries[0];
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
kWatchTime1.InMilliseconds() / kUnderflowCount1);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties1->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties1->natural_size.height());
// The second record should have...
entry = entries[1];
EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
kWatchTime2.InMilliseconds() / kUnderflowCount2);
EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
EXPECT_UKM(
UkmEntry::kAudioEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
EXPECT_UKM(
UkmEntry::kVideoEncryptionSchemeName,
static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties2->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
secondary_properties2->natural_size.height());
}
#undef EXPECT_UKM
#undef EXPECT_NO_UKM
#undef EXPECT_HAS_UKM
TEST_F(WatchTimeRecorderTest, DISABLED_PrintExpectedDecoderNameHashes) {
const std::string kDecoderNames[] = {
"FFmpegAudioDecoder", "FFmpegVideoDecoder", "GpuVideoDecoder",
"MojoVideoDecoder", "MojoAudioDecoder", "VpxVideoDecoder",
"AomVideoDecoder", "DecryptingAudioDecoder", "DecryptingVideoDecoder",
"Dav1dVideoDecoder", "FuchsiaVideoDecoder"};
printf("%18s = 0\n", "None");
for (const auto& name : kDecoderNames)
printf("%18s = 0x%x\n", name.c_str(), base::PersistentHash(name));
}
} // namespace media