// 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 #include #include #include #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/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, base::BindRepeating(&WatchTimeRecorderTest::GetSourceId, base::Unretained(this)), base::BindRepeating( []() { return learning::FeatureValue(0); }) /* origin callback */, VideoDecodePerfHistory::SaveCallback(), MediaMetricsProvider::GetLearningSessionCallback(), base::BindRepeating( &WatchTimeRecorderTest::GetRecordAggregateWatchTimeCallback, base::Unretained(this)), provider_.BindNewPipeAndPassReceiver()); } ~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), wtr_.BindNewPipeAndPassReceiver()); } 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& keys, base::TimeDelta value) { for (int i = 0; i <= static_cast(WatchTimeKey::kWatchTimeKeyMax); ++i) { const base::StringPiece test_key = ConvertWatchTimeKeyToStringForUma(static_cast(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& full_key_list, const std::vector& 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& keys, base::TimeDelta value) { ExpectHelper(mtbr_keys_, keys, value.InMilliseconds()); } void ExpectZeroRebuffers(const std::vector& keys) { ExpectHelper(smooth_keys_, keys, 0); } void ExpectRebuffers(const std::vector& keys, int count) { ExpectHelper(smooth_keys_, keys, count); } void ExpectAacAudioCodecProfileHistogram(AudioCodecProfile profile) { constexpr char kAudioCodecProfileHistogram[] = "Media.AudioCodecProfile.AAC"; histogram_tester_->ExpectUniqueSample(kAudioCodecProfileHistogram, static_cast(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& 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)); } mojom::SecondaryPlaybackPropertiesPtr CreateSecondaryProperties() { return mojom::SecondaryPlaybackProperties::New( kCodecAAC, kCodecH264, AudioCodecProfile::kUnknown, H264PROFILE_MAIN, "", "", EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted, gfx::Size(800, 600)); } ukm::SourceId GetSourceId() { return source_id_; } MediaMetricsProvider::RecordAggregateWatchTimeCallback GetRecordAggregateWatchTimeCallback() { return base::BindRepeating( [](base::WeakPtr 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 provider_; std::unique_ptr histogram_tester_; std::unique_ptr test_recorder_; ukm::SourceId source_id_; mojo::Remote wtr_; const std::vector computation_keys_; const std::vector mtbr_keys_; const std::vector smooth_keys_; const std::vector 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(WatchTimeKey::kWatchTimeKeyMax); ++i) { const WatchTimeKey key = static_cast(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(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, 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(CreateSecondaryProperties()); 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 = CreateSecondaryProperties(); 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::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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::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, secondary_properties->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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::SecondaryPlaybackPropertiesPtr secondary_properties = mojom::SecondaryPlaybackProperties::New( kCodecAAC, kCodecH264, AudioCodecProfile::kXHE_AAC, H264PROFILE_MAIN, "", "", EncryptionScheme::kCenc, EncryptionScheme::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(); 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, secondary_properties->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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::SecondaryPlaybackPropertiesPtr secondary_properties = mojom::SecondaryPlaybackProperties::New( kCodecOpus, kCodecVP9, AudioCodecProfile::kUnknown, VP9PROFILE_PROFILE0, "", "", EncryptionScheme::kUnencrypted, EncryptionScheme::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); constexpr base::TimeDelta kUnderflowDuration = base::TimeDelta::FromMilliseconds(500); wtr_->UpdateUnderflowDuration(2, kUnderflowDuration); wtr_->UpdateVideoDecodeStats(10, 2); 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::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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::SecondaryPlaybackPropertiesPtr secondary_properties = CreateSecondaryProperties(); 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(); if (secondary_properties->audio_codec == kCodecAAC) { 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, secondary_properties->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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::SecondaryPlaybackPropertiesPtr secondary_properties = CreateSecondaryProperties(); Initialize(properties.Clone()); wtr_->UpdateSecondaryProperties(secondary_properties.Clone()); wtr_->OnDurationChanged(base::TimeDelta::FromSeconds(12345)); wtr_.reset(); base::RunLoop().RunUntilIdle(); if (secondary_properties->audio_codec == kCodecAAC) { 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, secondary_properties->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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::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 == kCodecAAC) { 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, secondary_properties->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(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(secondary_properties->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::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); } } // 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, AudioCodecProfile::kUnknown, VIDEO_CODEC_PROFILE_UNKNOWN, "", "", EncryptionScheme::kUnencrypted, EncryptionScheme::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, AudioCodecProfile::kXHE_AAC, H264PROFILE_MAIN, "FFmpegAudioDecoder", "FFmpegVideoDecoder", 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::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, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM( UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties2->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties2->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties2->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, AudioCodecProfile::kUnknown, VP9PROFILE_PROFILE0, "MojoAudioDecoder", "MojoVideoDecoder", EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted, gfx::Size(400, 300)); Initialize(properties.Clone()); wtr_->UpdateSecondaryProperties(secondary_properties1.Clone()); constexpr base::TimeDelta kUnderflowDuration = base::TimeDelta::FromMilliseconds(250); constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(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( kCodecAAC, kCodecH264, AudioCodecProfile::kUnknown, H264PROFILE_MAIN, "FFmpegAudioDecoder", "FFmpegVideoDecoder", EncryptionScheme::kCenc, EncryptionScheme::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)); 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::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, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties1->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties1->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties1->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties2->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties2->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties2->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, AudioCodecProfile::kUnknown, VP9PROFILE_PROFILE0, "MojoAudioDecoder", "MojoVideoDecoder", EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted, gfx::Size(400, 300)); Initialize(properties.Clone()); wtr_->UpdateSecondaryProperties(secondary_properties1.Clone()); constexpr base::TimeDelta kUnderflowDuration = base::TimeDelta::FromMilliseconds(250); constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(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( kCodecAAC, kCodecH264, AudioCodecProfile::kXHE_AAC, H264PROFILE_MAIN, "FFmpegAudioDecoder", "FFmpegVideoDecoder", 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::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, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties1->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties1->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties1->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties2->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties2->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties2->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, AudioCodecProfile::kUnknown, VP9PROFILE_PROFILE0, "MojoAudioDecoder", "MojoVideoDecoder", EncryptionScheme::kCbcs, EncryptionScheme::kCbcs, gfx::Size(400, 300)); Initialize(properties.Clone()); wtr_->UpdateSecondaryProperties(secondary_properties1.Clone()); constexpr base::TimeDelta kUnderflowDuration = base::TimeDelta::FromMilliseconds(250); constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(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( kCodecAAC, kCodecH264, AudioCodecProfile::kXHE_AAC, H264PROFILE_MAIN, "FFmpegAudioDecoder", "FFmpegVideoDecoder", EncryptionScheme::kUnencrypted, EncryptionScheme::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::kCompletedRebuffersCountName, kUnderflowCount1); EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, kUnderflowDuration.InMilliseconds()); EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1); EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties1->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties1->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties1->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties2->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties2->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties2->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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::SecondaryPlaybackPropertiesPtr secondary_properties1 = mojom::SecondaryPlaybackProperties::New( kCodecOpus, kCodecVP9, AudioCodecProfile::kUnknown, VP9PROFILE_PROFILE0, "MojoAudioDecoder", "MojoVideoDecoder", EncryptionScheme::kCbcs, EncryptionScheme::kCbcs, gfx::Size(400, 300)); Initialize(properties.Clone()); wtr_->UpdateSecondaryProperties(secondary_properties1.Clone()); constexpr base::TimeDelta kUnderflowDuration = base::TimeDelta::FromMilliseconds(250); constexpr base::TimeDelta kWatchTime1 = base::TimeDelta::FromSeconds(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( kCodecAAC, kCodecH264, AudioCodecProfile::kXHE_AAC, H264PROFILE_MAIN, "FFmpegAudioDecoder", "FFmpegVideoDecoder", EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted, 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); // 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::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::kCompletedRebuffersCountName, kUnderflowCount1 - 1); EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, kUnderflowDuration.InMilliseconds()); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties1->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties1->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties1->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kAudioCodecProfileName, static_cast(secondary_properties2->audio_codec_profile)); EXPECT_UKM(UkmEntry::kVideoCodecProfileName, secondary_properties2->video_codec_profile); EXPECT_UKM( UkmEntry::kAudioEncryptionSchemeName, static_cast(secondary_properties2->audio_encryption_scheme)); EXPECT_UKM( UkmEntry::kVideoEncryptionSchemeName, static_cast(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", "MediaPlayer", "Gav1VideoDecoder"}; printf("%18s = 0\n", "None"); for (const auto& name : kDecoderNames) printf("%18s = 0x%08x\n", name.c_str(), base::PersistentHash(name)); } } // namespace media