blob: 3eb2fddb05dedabc23f46d999b94a2d440a95f97 [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/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_message_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/ukm/test_ukm_recorder.h"
#include "media/base/video_codecs.h"
#include "media/base/watch_time_keys.h"
#include "media/mojo/services/media_metrics_provider.h"
#include "mojo/public/cpp/bindings/remote.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 content {
class RenderFrameHostDelegate;
} // namespace content
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, GetSourceId(),
learning::FeatureValue(0), VideoDecodePerfHistory::SaveCallback(),
MediaMetricsProvider::GetLearningSessionCallback(),
base::BindRepeating(
&WatchTimeRecorderTest::GetRecordAggregateWatchTimeCallback,
base::Unretained(this)),
provider_.BindNewPipeAndPassReceiver());
}
WatchTimeRecorderTest(const WatchTimeRecorderTest&) = delete;
WatchTimeRecorderTest& operator=(const WatchTimeRecorderTest&) = delete;
~WatchTimeRecorderTest() override { base::RunLoop().RunUntilIdle(); }
void Initialize(mojom::PlaybackPropertiesPtr properties) {
provider_->Initialize(properties->is_mse,
properties->is_mse ? mojom::MediaURLScheme::kUnknown
: mojom::MediaURLScheme::kHttp,
properties->media_stream_type);
provider_->AcquireWatchTimeRecorder(std::move(properties),
wtr_.BindNewPipeAndPassReceiver());
}
void Initialize(bool has_audio,
bool has_video,
bool is_mse,
bool is_encrypted,
mojom::MediaStreamType media_stream_type =
mojom::MediaStreamType::kNone) {
Initialize(mojom::PlaybackProperties::New(has_audio, has_video, false,
false, is_mse, is_encrypted,
false, media_stream_type));
}
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, 0);
} else {
histogram_tester_->ExpectUniqueSample(test_key, 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, 0);
else
histogram_tester_->ExpectUniqueSample(key, 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 ExpectAacAudioCodecProfileHistogram(AudioCodecProfile profile) {
constexpr char kAudioCodecProfileHistogram[] =
"Media.AudioCodecProfile.AAC";
histogram_tester_->ExpectUniqueSample(kAudioCodecProfileHistogram,
static_cast<int64_t>(profile), 1);
}
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_ = std::make_unique<base::HistogramTester>();
// Ensure cleared global before attempting to create a new TestUkmReporter.
test_recorder_.reset();
test_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
test_recorder_->UpdateSourceURL(source_id_, GURL(kTestOrigin));
}
mojom::SecondaryPlaybackPropertiesPtr CreateSecondaryProperties() {
return mojom::SecondaryPlaybackProperties::New(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kUnknown,
H264PROFILE_MAIN, AudioDecoderType::kUnknown,
VideoDecoderType::kUnknown, EncryptionScheme::kUnencrypted,
EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
}
ukm::SourceId GetSourceId() { return source_id_; }
MediaMetricsProvider::RecordAggregateWatchTimeCallback
GetRecordAggregateWatchTimeCallback() {
return base::BindRepeating(
[](base::WeakPtr<content::RenderFrameHostDelegate> delegate,
GURL last_committed_url, base::TimeDelta total_watch_time,
base::TimeDelta time_stamp, bool has_video, bool has_audio) {
// Do nothing as this mock callback will never be called.
},
nullptr, GURL());
}
MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta());
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
mojo::Remote<mojom::MediaMetricsProvider> provider_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_recorder_;
ukm::SourceId source_id_;
mojo::Remote<mojom::WatchTimeRecorder> 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_;
};
TEST_F(WatchTimeRecorderTest, TestBasicReporting) {
constexpr base::TimeDelta kWatchTime1 = base::Seconds(25);
constexpr base::TimeDelta kWatchTime2 = base::Seconds(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)
: std::string(key_str));
// 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(CreateSecondaryProperties());
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, TestBasicReportingMediaStream) {
constexpr base::TimeDelta kWatchTime1 = base::Seconds(25);
constexpr base::TimeDelta kWatchTime2 = base::Seconds(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)
: std::string(key_str));
// 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,
mojom::MediaStreamType::kLocalDeviceCapture);
wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());
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({}, base::TimeDelta());
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers({});
ExpectNoUkmWatchTime();
ResetMetricRecorders();
wtr_.reset();
base::RunLoop().RunUntilIdle();
ExpectWatchTime({}, base::TimeDelta());
ExpectMtbrTime({}, base::TimeDelta());
ExpectZeroRebuffers({});
// UKM watch time should be recorded even with no UMA.
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::Seconds(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::Seconds(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, 0);
}
TEST_F(WatchTimeRecorderTest, TestRebufferingMetricsMediaStream) {
Initialize(true, false, true, true,
mojom::MediaStreamType::kLocalDeviceCapture);
constexpr base::TimeDelta kWatchTime = base::Seconds(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({}, base::TimeDelta());
ExpectRebuffers({}, 0);
// 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());
ExpectRebuffers({}, 0);
}
TEST_F(WatchTimeRecorderTest, TestDiscardMetrics) {
Initialize(true, false, true, true);
wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());
constexpr base::TimeDelta kWatchTime = base::Seconds(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, kWatchTime.InMilliseconds(), 1);
}
// UKM watch time won't be logged because we aren't sending "All" keys.
ExpectUkmWatchTime({}, base::TimeDelta());
}
TEST_F(WatchTimeRecorderTest, TestDiscardMetricsMediaStream) {
Initialize(true, false, true, true,
mojom::MediaStreamType::kLocalDeviceCapture);
wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());
constexpr base::TimeDelta kWatchTime = base::Seconds(5);
for (auto key : computation_keys_)
wtr_->RecordWatchTime(key, kWatchTime);
// Trigger finalization of everything.
wtr_.reset();
base::RunLoop().RunUntilIdle();
// No watch time and no discard metrics should be logged.
ExpectWatchTime({}, base::TimeDelta());
for (auto key : discard_keys_) {
histogram_tester_->ExpectTotalCount(key, 0);
}
// 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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
CreateSecondaryProperties();
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
// Verify that UKM is reported along with the watch time.
constexpr base::TimeDelta kWatchTime = base::Seconds(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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
CreateSecondaryProperties();
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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
H264PROFILE_MAIN, AudioDecoderType::kUnknown,
VideoDecoderType::kUnknown, EncryptionScheme::kCenc,
EncryptionScheme::kCbcs, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
constexpr base::TimeDelta kWatchTime = base::Seconds(4);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
wtr_.reset();
base::RunLoop().RunUntilIdle();
ExpectAacAudioCodecProfileHistogram(
secondary_properties->audio_codec_profile);
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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
VP9PROFILE_PROFILE0, AudioDecoderType::kUnknown,
VideoDecoderType::kUnknown, EncryptionScheme::kUnencrypted,
EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
constexpr base::TimeDelta kWatchTime = base::Seconds(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);
constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(500);
wtr_->UpdateUnderflowDuration(2, kUnderflowDuration);
wtr_->UpdateVideoDecodeStats(10, 2);
wtr_->OnError(PIPELINE_ERROR_DECODE);
secondary_properties->audio_decoder = AudioDecoderType::kMojo;
secondary_properties->video_decoder = VideoDecoderType::kMojo;
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
wtr_->SetAutoplayInitiated(true);
wtr_->OnDurationChanged(base::Seconds(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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 3);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 2);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 10);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 2);
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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
CreateSecondaryProperties();
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
constexpr base::TimeDelta kWatchTime = base::Seconds(54);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoBackgroundAll, kWatchTime);
wtr_.reset();
base::RunLoop().RunUntilIdle();
if (secondary_properties->audio_codec == AudioCodec::kAAC) {
ExpectAacAudioCodecProfileHistogram(
secondary_properties->audio_codec_profile);
}
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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
CreateSecondaryProperties();
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
wtr_->OnDurationChanged(base::Seconds(12345));
wtr_.reset();
base::RunLoop().RunUntilIdle();
if (secondary_properties->audio_codec == AudioCodec::kAAC) {
ExpectAacAudioCodecProfileHistogram(
secondary_properties->audio_codec_profile);
}
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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
CreateSecondaryProperties();
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
wtr_->OnDurationChanged(kInfiniteDuration);
wtr_.reset();
base::RunLoop().RunUntilIdle();
if (secondary_properties->audio_codec == AudioCodec::kAAC) {
ExpectAacAudioCodecProfileHistogram(
secondary_properties->audio_codec_profile);
}
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,
static_cast<int>(secondary_properties->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties->video_codec_profile);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 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);
}
}
TEST_F(WatchTimeRecorderTest, BasicUkmMediaStreamType) {
std::vector<mojom::MediaStreamType> media_stream_types{
mojom::MediaStreamType::kLocalElementCapture,
mojom::MediaStreamType::kLocalDeviceCapture,
mojom::MediaStreamType::kLocalTabCapture,
mojom::MediaStreamType::kLocalDesktopCapture,
mojom::MediaStreamType::kLocalDisplayCapture,
mojom::MediaStreamType::kRemote,
mojom::MediaStreamType::kNone,
};
for (const auto& media_stream_type : media_stream_types) {
mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
true, true, false, false, false, false, false, media_stream_type);
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());
constexpr base::TimeDelta kWatchTime = base::Seconds(1);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
wtr_.reset();
base::RunLoop().RunUntilIdle();
const auto& entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
ASSERT_EQ(1u, entries.size());
// Check that the media stream type is set correctly.
for (const auto* entry : entries) {
EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
static_cast<int64_t>(media_stream_type));
}
ResetMetricRecorders();
}
}
// 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, mojom::MediaStreamType::kNone);
Initialize(properties.Clone());
constexpr base::TimeDelta kWatchTime = base::Seconds(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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kUnknown, VideoCodec::kUnknown,
AudioCodecProfile::kUnknown, VIDEO_CODEC_PROFILE_UNKNOWN,
AudioDecoderType::kUnknown, VideoDecoderType::kUnknown,
EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
gfx::Size(800, 600));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kWatchTime = base::Seconds(54);
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
EncryptionScheme::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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
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::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties2->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties2->video_codec));
EXPECT_UKM(
UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties2->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties2->video_codec_profile);
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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);
constexpr int kDecodedFrameCount1 = 10;
constexpr int kDroppedFrameCount1 = 2;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kUnknown,
H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
VideoDecoderType::kFFmpeg, EncryptionScheme::kCenc,
EncryptionScheme::kCenc, gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
constexpr base::TimeDelta kWatchTime2 = base::Seconds(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::Seconds(5125));
constexpr int kDecodedFrameCount2 = 20;
constexpr int kDroppedFrameCount2 = 10;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1 + kDecodedFrameCount2,
kDroppedFrameCount1 + kDroppedFrameCount2);
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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
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::kCompletedRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties1->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties1->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties1->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties1->video_codec_profile);
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::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount2);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount2);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties2->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties2->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties2->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties2->video_codec_profile);
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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);
constexpr int kDecodedFrameCount1 = 10;
constexpr int kDroppedFrameCount1 = 2;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
EncryptionScheme::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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
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::kCompletedRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties1->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties1->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties1->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties1->video_codec_profile);
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::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties2->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties2->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties2->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties2->video_codec_profile);
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::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
EncryptionScheme::kCbcs, EncryptionScheme::kCbcs,
gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);
constexpr int kDecodedFrameCount1 = 10;
constexpr int kDroppedFrameCount1 = 2;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);
// 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(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
constexpr base::TimeDelta kWatchTime2 = base::Seconds(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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
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::kCompletedRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties1->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties1->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties1->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties1->video_codec_profile);
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::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties2->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties2->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties2->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties2->video_codec_profile);
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, MultipleSecondaryPropertiesRebufferCarryover) {
mojom::PlaybackPropertiesPtr properties =
mojom::PlaybackProperties::New(true, true, false, false, true, true,
false, mojom::MediaStreamType::kNone);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
EncryptionScheme::kCbcs, EncryptionScheme::kCbcs,
gfx::Size(400, 300));
Initialize(properties.Clone());
wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
const int kUnderflowCount1 = 2;
wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
wtr_->UpdateUnderflowCount(kUnderflowCount1);
// Complete all but one of the rebuffers in this update.
wtr_->UpdateUnderflowDuration(kUnderflowCount1 - 1, kUnderflowDuration);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New(
AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
constexpr base::TimeDelta kWatchTime2 = base::Seconds(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);
// Complete the last underflow in the new property set. Unfortunately this
// means it will now be associated with this block of watch time. Use a non
// integer multiplier to avoid incorrect carry over being hidden.
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration * 1.5);
wtr_->OnError(PIPELINE_ERROR_DECODE);
wtr_->OnDurationChanged(base::Seconds(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::kMediaStreamTypeName,
static_cast<int64_t>(properties->media_stream_type));
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::kCompletedRebuffersCountName, kUnderflowCount1 - 1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties1->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties1->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties1->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties1->video_codec_profile);
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::kCompletedRebuffersCountName, 1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
(kUnderflowDuration * 1.5 - kUnderflowDuration).InMilliseconds());
EXPECT_UKM(UkmEntry::kAudioCodecName,
static_cast<int>(secondary_properties2->audio_codec));
EXPECT_UKM(UkmEntry::kVideoCodecName,
static_cast<int>(secondary_properties2->video_codec));
EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
static_cast<int64_t>(secondary_properties2->audio_codec_profile));
EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
secondary_properties2->video_codec_profile);
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
} // namespace media