diff options
Diffstat (limited to 'chromium/media/blink/watch_time_reporter.cc')
-rw-r--r-- | chromium/media/blink/watch_time_reporter.cc | 600 |
1 files changed, 260 insertions, 340 deletions
diff --git a/chromium/media/blink/watch_time_reporter.cc b/chromium/media/blink/watch_time_reporter.cc index 49a4ebf7b24..52ffc08b7b0 100644 --- a/chromium/media/blink/watch_time_reporter.cc +++ b/chromium/media/blink/watch_time_reporter.cc @@ -18,8 +18,29 @@ static bool IsOnBatteryPower() { return false; } +// Helper function for managing property changes. If the watch time timer is +// running it sets the pending value otherwise it sets the current value and +// then returns true if the component needs finalize. +enum class PropertyAction { kNoActionRequired, kFinalizeRequired }; +template <typename T> +PropertyAction HandlePropertyChange(T new_value, + bool is_timer_running, + WatchTimeComponent<T>* component) { + if (!component) + return PropertyAction::kNoActionRequired; + + if (is_timer_running) + component->SetPendingValue(new_value); + else + component->SetCurrentValue(new_value); + + return component->NeedsFinalize() ? PropertyAction::kFinalizeRequired + : PropertyAction::kNoActionRequired; +} + WatchTimeReporter::WatchTimeReporter( mojom::PlaybackPropertiesPtr properties, + const gfx::Size& initial_natural_size, GetMediaTimeCB get_media_time_cb, mojom::MediaMetricsProvider* provider, scoped_refptr<base::SequencedTaskRunner> task_runner, @@ -27,6 +48,7 @@ WatchTimeReporter::WatchTimeReporter( : WatchTimeReporter(std::move(properties), false /* is_background */, false /* is_muted */, + initial_natural_size, std::move(get_media_time_cb), provider, task_runner, @@ -36,6 +58,7 @@ WatchTimeReporter::WatchTimeReporter( mojom::PlaybackPropertiesPtr properties, bool is_background, bool is_muted, + const gfx::Size& initial_natural_size, GetMediaTimeCB get_media_time_cb, mojom::MediaMetricsProvider* provider, scoped_refptr<base::SequencedTaskRunner> task_runner, @@ -43,6 +66,7 @@ WatchTimeReporter::WatchTimeReporter( : properties_(std::move(properties)), is_background_(is_background), is_muted_(is_muted), + initial_natural_size_(initial_natural_size), get_media_time_cb_(std::move(get_media_time_cb)), reporting_timer_(tick_clock) { DCHECK(!get_media_time_cb_.is_null()); @@ -67,6 +91,14 @@ WatchTimeReporter::WatchTimeReporter( reporting_timer_.SetTaskRunner(task_runner); + base_component_ = CreateBaseComponent(); + power_component_ = CreatePowerComponent(); + if (!is_background_) { + controls_component_ = CreateControlsComponent(); + if (properties_->has_video) + display_type_component_ = CreateDisplayTypeComponent(); + } + // If this is a sub-reporter or we shouldn't report watch time, we're done. We // don't support muted+background reporting currently. if (is_background_ || is_muted_ || !ShouldReportWatchTime()) @@ -79,7 +111,8 @@ WatchTimeReporter::WatchTimeReporter( prop_copy->is_background = true; background_reporter_.reset(new WatchTimeReporter( std::move(prop_copy), true /* is_background */, false /* is_muted */, - get_media_time_cb_, provider, task_runner, tick_clock)); + initial_natural_size_, get_media_time_cb_, provider, task_runner, + tick_clock)); // Muted watch time is only reported for audio+video playback. if (!properties_->has_video || !properties_->has_audio) @@ -91,7 +124,8 @@ WatchTimeReporter::WatchTimeReporter( prop_copy->is_muted = true; muted_reporter_.reset(new WatchTimeReporter( std::move(prop_copy), false /* is_background */, true /* is_muted */, - get_media_time_cb_, provider, task_runner, tick_clock)); + initial_natural_size_, get_media_time_cb_, provider, task_runner, + tick_clock)); } WatchTimeReporter::~WatchTimeReporter() { @@ -99,6 +133,7 @@ WatchTimeReporter::~WatchTimeReporter() { muted_reporter_.reset(); // This is our last chance, so finalize now if there's anything remaining. + in_shutdown_ = true; MaybeFinalizeWatchTime(FinalizeTime::IMMEDIATELY); if (base::PowerMonitor* pm = base::PowerMonitor::Get()) pm->RemoveObserver(this); @@ -111,6 +146,7 @@ void WatchTimeReporter::OnPlaying() { muted_reporter_->OnPlaying(); is_playing_ = true; + is_seeking_ = false; MaybeStartReportingTimer(get_media_time_cb_.Run()); } @@ -132,6 +168,7 @@ void WatchTimeReporter::OnSeeking() { // Seek is a special case that does not have hysteresis, when this is called // the seek is imminent, so finalize the previous playback immediately. + is_seeking_ = true; MaybeFinalizeWatchTime(FinalizeTime::IMMEDIATELY); } @@ -210,71 +247,39 @@ void WatchTimeReporter::OnUnderflow() { } void WatchTimeReporter::OnNativeControlsEnabled() { - if (muted_reporter_) - muted_reporter_->OnNativeControlsEnabled(); - - if (!reporting_timer_.IsRunning()) { - has_native_controls_ = true; - return; - } - - if (end_timestamp_for_controls_ != kNoTimestamp) { - end_timestamp_for_controls_ = kNoTimestamp; - return; - } - - end_timestamp_for_controls_ = get_media_time_cb_.Run(); - reporting_timer_.Start(FROM_HERE, reporting_interval_, this, - &WatchTimeReporter::UpdateWatchTime); + OnNativeControlsChanged(true); } void WatchTimeReporter::OnNativeControlsDisabled() { - if (muted_reporter_) - muted_reporter_->OnNativeControlsDisabled(); - - if (!reporting_timer_.IsRunning()) { - has_native_controls_ = false; - return; - } - - if (end_timestamp_for_controls_ != kNoTimestamp) { - end_timestamp_for_controls_ = kNoTimestamp; - return; - } - - end_timestamp_for_controls_ = get_media_time_cb_.Run(); - reporting_timer_.Start(FROM_HERE, reporting_interval_, this, - &WatchTimeReporter::UpdateWatchTime); + OnNativeControlsChanged(false); } void WatchTimeReporter::OnDisplayTypeInline() { - OnDisplayTypeChanged(blink::WebMediaPlayer::DisplayType::kInline); + OnDisplayTypeChanged(DisplayType::kInline); } void WatchTimeReporter::OnDisplayTypeFullscreen() { - OnDisplayTypeChanged(blink::WebMediaPlayer::DisplayType::kFullscreen); + OnDisplayTypeChanged(DisplayType::kFullscreen); } void WatchTimeReporter::OnDisplayTypePictureInPicture() { - OnDisplayTypeChanged(blink::WebMediaPlayer::DisplayType::kPictureInPicture); + OnDisplayTypeChanged(DisplayType::kPictureInPicture); } -void WatchTimeReporter::SetAudioDecoderName(const std::string& name) { - DCHECK(properties_->has_audio); - recorder_->SetAudioDecoderName(name); - if (background_reporter_) - background_reporter_->SetAudioDecoderName(name); - if (muted_reporter_) - muted_reporter_->SetAudioDecoderName(name); -} +void WatchTimeReporter::UpdateSecondaryProperties( + mojom::SecondaryPlaybackPropertiesPtr secondary_properties) { + // Flush any unrecorded watch time before updating the secondary properties to + // ensure the UKM record is finalized with up-to-date watch time information. + if (reporting_timer_.IsRunning()) + RecordWatchTime(); -void WatchTimeReporter::SetVideoDecoderName(const std::string& name) { - DCHECK(properties_->has_video); - recorder_->SetVideoDecoderName(name); - if (background_reporter_) - background_reporter_->SetVideoDecoderName(name); + recorder_->UpdateSecondaryProperties(secondary_properties.Clone()); + if (background_reporter_) { + background_reporter_->UpdateSecondaryProperties( + secondary_properties.Clone()); + } if (muted_reporter_) - muted_reporter_->SetVideoDecoderName(name); + muted_reporter_->UpdateSecondaryProperties(std::move(secondary_properties)); } void WatchTimeReporter::SetAutoplayInitiated(bool autoplay_initiated) { @@ -285,88 +290,97 @@ void WatchTimeReporter::SetAutoplayInitiated(bool autoplay_initiated) { muted_reporter_->SetAutoplayInitiated(autoplay_initiated); } +void WatchTimeReporter::OnDurationChanged(base::TimeDelta duration) { + recorder_->OnDurationChanged(duration); + if (background_reporter_) + background_reporter_->OnDurationChanged(duration); + if (muted_reporter_) + muted_reporter_->OnDurationChanged(duration); +} + void WatchTimeReporter::OnPowerStateChange(bool on_battery_power) { - if (!reporting_timer_.IsRunning()) - return; + if (HandlePropertyChange<bool>(on_battery_power, reporting_timer_.IsRunning(), + power_component_.get()) == + PropertyAction::kFinalizeRequired) { + RestartTimerForHysteresis(); + } +} - // Defer changing |is_on_battery_power_| until the next watch time report to - // avoid momentary power changes from affecting the results. - if (is_on_battery_power_ != on_battery_power) { - end_timestamp_for_power_ = get_media_time_cb_.Run(); +void WatchTimeReporter::OnNativeControlsChanged(bool has_native_controls) { + if (muted_reporter_) + muted_reporter_->OnNativeControlsChanged(has_native_controls); - // Restart the reporting timer so the full hysteresis is afforded. - reporting_timer_.Start(FROM_HERE, reporting_interval_, this, - &WatchTimeReporter::UpdateWatchTime); - return; + if (HandlePropertyChange<bool>( + has_native_controls, reporting_timer_.IsRunning(), + controls_component_.get()) == PropertyAction::kFinalizeRequired) { + RestartTimerForHysteresis(); } +} + +void WatchTimeReporter::OnDisplayTypeChanged(DisplayType display_type) { + if (muted_reporter_) + muted_reporter_->OnDisplayTypeChanged(display_type); - end_timestamp_for_power_ = kNoTimestamp; + if (HandlePropertyChange<DisplayType>( + display_type, reporting_timer_.IsRunning(), + display_type_component_.get()) == PropertyAction::kFinalizeRequired) { + RestartTimerForHysteresis(); + } } -bool WatchTimeReporter::ShouldReportWatchTime() { +bool WatchTimeReporter::ShouldReportWatchTime() const { // Report listen time or watch time for videos of sufficient size. return properties_->has_video - ? (properties_->natural_size.height() >= - kMinimumVideoSize.height() && - properties_->natural_size.width() >= kMinimumVideoSize.width()) + ? (initial_natural_size_.height() >= kMinimumVideoSize.height() && + initial_natural_size_.width() >= kMinimumVideoSize.width()) : properties_->has_audio; } +bool WatchTimeReporter::ShouldReportingTimerRun() const { + // TODO(dalecurtis): We should only consider |volume_| when there is actually + // an audio track; requires updating lots of tests to fix. + return ShouldReportWatchTime() && is_playing_ && volume_ && is_visible_ && + !in_shutdown_ && !is_seeking_; +} + void WatchTimeReporter::MaybeStartReportingTimer( base::TimeDelta start_timestamp) { DCHECK_NE(start_timestamp, kInfiniteDuration); DCHECK_GE(start_timestamp, base::TimeDelta()); - // Don't start the timer if any of our state indicates we shouldn't; this - // check is important since the various event handlers do not have to care - // about the state of other events. - // - // TODO(dalecurtis): We should only consider |volume_| when there is actually - // an audio track; requires updating lots of tests to fix. - if (!ShouldReportWatchTime() || !is_playing_ || !volume_ || !is_visible_) { - // If we reach this point the timer should already have been stopped or - // there is a pending finalize in flight. - DCHECK(!reporting_timer_.IsRunning() || end_timestamp_ != kNoTimestamp); + // Don't start the timer if our state indicates we shouldn't; this check is + // important since the various event handlers do not have to care about the + // state of other events. + const bool should_start = ShouldReportingTimerRun(); + if (reporting_timer_.IsRunning()) { + base_component_->SetPendingValue(should_start); return; } - // If we haven't finalized the last watch time metrics yet, count this - // playback as a continuation of the previous metrics. - if (end_timestamp_ != kNoTimestamp) { - DCHECK(reporting_timer_.IsRunning()); - end_timestamp_ = kNoTimestamp; - return; - } - - // Don't restart the timer if it's already running. - if (reporting_timer_.IsRunning()) + base_component_->SetCurrentValue(should_start); + if (!should_start) return; underflow_count_ = 0; pending_underflow_events_.clear(); - last_media_timestamp_ = last_media_power_timestamp_ = - last_media_controls_timestamp_ = end_timestamp_for_power_ = - last_media_display_type_timestamp_ = end_timestamp_for_display_type_ = - kNoTimestamp; - is_on_battery_power_ = IsOnBatteryPower(); - display_type_for_recording_ = display_type_; - start_timestamp_ = start_timestamp_for_power_ = - start_timestamp_for_controls_ = start_timestamp_for_display_type_ = - start_timestamp; + + base_component_->OnReportingStarted(start_timestamp); + power_component_->OnReportingStarted(start_timestamp); + + if (controls_component_) + controls_component_->OnReportingStarted(start_timestamp); + if (display_type_component_) + display_type_component_->OnReportingStarted(start_timestamp); + reporting_timer_.Start(FROM_HERE, reporting_interval_, this, &WatchTimeReporter::UpdateWatchTime); } void WatchTimeReporter::MaybeFinalizeWatchTime(FinalizeTime finalize_time) { - // Don't finalize if the timer is already stopped. - if (!reporting_timer_.IsRunning()) + if (HandlePropertyChange<bool>( + ShouldReportingTimerRun(), reporting_timer_.IsRunning(), + base_component_.get()) == PropertyAction::kNoActionRequired) { return; - - // Don't trample an existing finalize; the first takes precedence. - if (end_timestamp_ == kNoTimestamp) { - end_timestamp_ = get_media_time_cb_.Run(); - DCHECK_NE(end_timestamp_, kInfiniteDuration); - DCHECK_GE(end_timestamp_, base::TimeDelta()); } if (finalize_time == FinalizeTime::IMMEDIATELY) { @@ -377,175 +391,31 @@ void WatchTimeReporter::MaybeFinalizeWatchTime(FinalizeTime finalize_time) { // Always restart the timer when finalizing, so that we allow for the full // length of |kReportingInterval| to elapse for hysteresis purposes. DCHECK_EQ(finalize_time, FinalizeTime::ON_NEXT_UPDATE); + RestartTimerForHysteresis(); +} + +void WatchTimeReporter::RestartTimerForHysteresis() { + // Restart the reporting timer so the full hysteresis is afforded. + DCHECK(reporting_timer_.IsRunning()); reporting_timer_.Start(FROM_HERE, reporting_interval_, this, &WatchTimeReporter::UpdateWatchTime); } -void WatchTimeReporter::UpdateWatchTime() { - DCHECK(ShouldReportWatchTime()); - - const bool is_finalizing = end_timestamp_ != kNoTimestamp; - const bool is_power_change_pending = end_timestamp_for_power_ != kNoTimestamp; - const bool is_controls_change_pending = - end_timestamp_for_controls_ != kNoTimestamp; - const bool is_display_type_change_pending = - end_timestamp_for_display_type_ != kNoTimestamp; - - // If we're finalizing the log, use the media time value at the time of - // finalization. +void WatchTimeReporter::RecordWatchTime() { + // If we're finalizing, use the media time at time of finalization. const base::TimeDelta current_timestamp = - is_finalizing ? end_timestamp_ : get_media_time_cb_.Run(); - DCHECK_NE(current_timestamp, kInfiniteDuration); - DCHECK_GE(current_timestamp, start_timestamp_); - - const base::TimeDelta elapsed = current_timestamp - start_timestamp_; - -#define RECORD_WATCH_TIME(key, value) \ - do { \ - recorder_->RecordWatchTime( \ - (properties_->has_video && properties_->has_audio) \ - ? (is_background_ \ - ? WatchTimeKey::kAudioVideoBackground##key \ - : (is_muted_ ? WatchTimeKey::kAudioVideoMuted##key \ - : WatchTimeKey::kAudioVideo##key)) \ - : properties_->has_video \ - ? (is_background_ ? WatchTimeKey::kVideoBackground##key \ - : WatchTimeKey::kVideo##key) \ - : (is_background_ ? WatchTimeKey::kAudioBackground##key \ - : WatchTimeKey::kAudio##key), \ - value); \ - } while (0) - - // Only report watch time after some minimum amount has elapsed. Don't update - // watch time if media time hasn't changed since the last run; this may occur - // if a seek is taking some time to complete or the playback is stalled for - // some reason. - if (last_media_timestamp_ != current_timestamp) { - last_media_timestamp_ = current_timestamp; - - if (elapsed > base::TimeDelta()) { - RECORD_WATCH_TIME(All, elapsed); - if (properties_->is_mse) - RECORD_WATCH_TIME(Mse, elapsed); - else - RECORD_WATCH_TIME(Src, elapsed); - - if (properties_->is_eme) - RECORD_WATCH_TIME(Eme, elapsed); - - if (properties_->is_embedded_media_experience) - RECORD_WATCH_TIME(EmbeddedExperience, elapsed); - } - } - - if (last_media_power_timestamp_ != current_timestamp) { - // We need a separate |last_media_power_timestamp_| since we don't always - // base the last watch time calculation on the current timestamp. - last_media_power_timestamp_ = - is_power_change_pending ? end_timestamp_for_power_ : current_timestamp; - - // Record watch time using the last known value for |is_on_battery_power_|; - // if there's a |pending_power_change_| use that to accurately finalize the - // last bits of time in the previous bucket. - DCHECK_GE(last_media_power_timestamp_, start_timestamp_for_power_); - const base::TimeDelta elapsed_power = - last_media_power_timestamp_ - start_timestamp_for_power_; - - // Again, only update watch time if any time has elapsed; we need to recheck - // the elapsed time here since the power source can change anytime. - if (elapsed_power > base::TimeDelta()) { - if (is_on_battery_power_) - RECORD_WATCH_TIME(Battery, elapsed_power); - else - RECORD_WATCH_TIME(Ac, elapsed_power); - } - } - -// Similar to RECORD_WATCH_TIME but ignores background watch time. -#define RECORD_FOREGROUND_WATCH_TIME(key, value) \ - do { \ - DCHECK(!is_background_); \ - recorder_->RecordWatchTime( \ - (properties_->has_video && properties_->has_audio) \ - ? (is_muted_ ? WatchTimeKey::kAudioVideoMuted##key \ - : WatchTimeKey::kAudioVideo##key) \ - : properties_->has_audio ? WatchTimeKey::kAudio##key \ - : WatchTimeKey::kVideo##key, \ - value); \ - } while (0) - - // Similar to the block above for controls. - if (!is_background_ && last_media_controls_timestamp_ != current_timestamp) { - last_media_controls_timestamp_ = is_controls_change_pending - ? end_timestamp_for_controls_ - : current_timestamp; - - DCHECK_GE(last_media_controls_timestamp_, start_timestamp_for_controls_); - const base::TimeDelta elapsed_controls = - last_media_controls_timestamp_ - start_timestamp_for_controls_; - - if (elapsed_controls > base::TimeDelta()) { - if (has_native_controls_) - RECORD_FOREGROUND_WATCH_TIME(NativeControlsOn, elapsed_controls); - else - RECORD_FOREGROUND_WATCH_TIME(NativeControlsOff, elapsed_controls); - } - } - -// Similar to RECORD_WATCH_TIME but ignores background and audio watch time. -#define RECORD_DISPLAY_WATCH_TIME(key, value) \ - do { \ - DCHECK(properties_->has_video); \ - DCHECK(!is_background_); \ - recorder_->RecordWatchTime( \ - properties_->has_audio \ - ? (is_muted_ ? WatchTimeKey::kAudioVideoMuted##key \ - : WatchTimeKey::kAudioVideo##key) \ - : WatchTimeKey::kVideo##key, \ - value); \ - } while (0) - - // Similar to the block above for display type. - if (!is_background_ && properties_->has_video && - last_media_display_type_timestamp_ != current_timestamp) { - last_media_display_type_timestamp_ = is_display_type_change_pending - ? end_timestamp_for_display_type_ - : current_timestamp; - - DCHECK_GE(last_media_display_type_timestamp_, - start_timestamp_for_display_type_); - const base::TimeDelta elapsed_display_type = - last_media_display_type_timestamp_ - start_timestamp_for_display_type_; - - if (elapsed_display_type > base::TimeDelta()) { - switch (display_type_for_recording_) { - case blink::WebMediaPlayer::DisplayType::kInline: - RECORD_DISPLAY_WATCH_TIME(DisplayInline, elapsed_display_type); - break; - case blink::WebMediaPlayer::DisplayType::kFullscreen: - RECORD_DISPLAY_WATCH_TIME(DisplayFullscreen, elapsed_display_type); - break; - case blink::WebMediaPlayer::DisplayType::kPictureInPicture: - RECORD_DISPLAY_WATCH_TIME(DisplayPictureInPicture, - elapsed_display_type); - break; - } - } - } - -#undef RECORD_WATCH_TIME -#undef RECORD_FOREGROUND_WATCH_TIME -#undef RECORD_DISPLAY_WATCH_TIME + base_component_->NeedsFinalize() ? base_component_->end_timestamp() + : get_media_time_cb_.Run(); // Pass along any underflow events which have occurred since the last report. if (!pending_underflow_events_.empty()) { - if (!is_finalizing) { + if (!base_component_->NeedsFinalize()) { // The maximum value here per period is ~5 events, so int cast is okay. underflow_count_ += static_cast<int>(pending_underflow_events_.size()); } else { // Only count underflow events prior to finalize. for (auto& ts : pending_underflow_events_) { - if (ts <= end_timestamp_) + if (ts <= base_component_->end_timestamp()) underflow_count_++; } } @@ -554,106 +424,156 @@ void WatchTimeReporter::UpdateWatchTime() { pending_underflow_events_.clear(); } - // Always send finalize, even if we don't currently have any data, it's - // harmless to send since nothing will be logged if we've already finalized. - if (is_finalizing) { - recorder_->FinalizeWatchTime({}); - } else { - std::vector<WatchTimeKey> keys_to_finalize; - if (is_power_change_pending) { - keys_to_finalize.insert( - keys_to_finalize.end(), - {WatchTimeKey::kAudioBattery, WatchTimeKey::kAudioAc, - WatchTimeKey::kAudioBackgroundBattery, - WatchTimeKey::kAudioBackgroundAc, WatchTimeKey::kAudioVideoBattery, - WatchTimeKey::kAudioVideoAc, - WatchTimeKey::kAudioVideoBackgroundBattery, - WatchTimeKey::kAudioVideoBackgroundAc, - WatchTimeKey::kAudioVideoMutedBattery, - WatchTimeKey::kAudioVideoMutedAc, WatchTimeKey::kVideoBattery, - WatchTimeKey::kVideoAc, WatchTimeKey::kVideoBackgroundAc, - WatchTimeKey::kVideoBackgroundBattery}); - } + // Record watch time for all components. + base_component_->RecordWatchTime(current_timestamp); + power_component_->RecordWatchTime(current_timestamp); + if (display_type_component_) + display_type_component_->RecordWatchTime(current_timestamp); + if (controls_component_) + controls_component_->RecordWatchTime(current_timestamp); +} - if (is_controls_change_pending) { - keys_to_finalize.insert(keys_to_finalize.end(), - {WatchTimeKey::kAudioNativeControlsOn, - WatchTimeKey::kAudioNativeControlsOff, - WatchTimeKey::kAudioVideoNativeControlsOn, - WatchTimeKey::kAudioVideoNativeControlsOff, - WatchTimeKey::kAudioVideoMutedNativeControlsOn, - WatchTimeKey::kAudioVideoMutedNativeControlsOff, - WatchTimeKey::kVideoNativeControlsOn, - WatchTimeKey::kVideoNativeControlsOff}); - } +void WatchTimeReporter::UpdateWatchTime() { + DCHECK(ShouldReportWatchTime()); - if (is_display_type_change_pending) { - keys_to_finalize.insert( - keys_to_finalize.end(), - {WatchTimeKey::kAudioVideoDisplayFullscreen, - WatchTimeKey::kAudioVideoDisplayInline, - WatchTimeKey::kAudioVideoDisplayPictureInPicture, - WatchTimeKey::kAudioVideoMutedDisplayFullscreen, - WatchTimeKey::kAudioVideoMutedDisplayInline, - WatchTimeKey::kAudioVideoMutedDisplayPictureInPicture, - WatchTimeKey::kVideoDisplayFullscreen, - WatchTimeKey::kVideoDisplayInline, - WatchTimeKey::kVideoDisplayPictureInPicture}); - } + // First record watch time. + RecordWatchTime(); + // Second, process any pending finalize events. + std::vector<WatchTimeKey> keys_to_finalize; + if (power_component_->NeedsFinalize()) + power_component_->Finalize(&keys_to_finalize); + if (display_type_component_ && display_type_component_->NeedsFinalize()) + display_type_component_->Finalize(&keys_to_finalize); + if (controls_component_ && controls_component_->NeedsFinalize()) + controls_component_->Finalize(&keys_to_finalize); + + // Then finalize the base component. + if (!base_component_->NeedsFinalize()) { if (!keys_to_finalize.empty()) recorder_->FinalizeWatchTime(keys_to_finalize); + return; } - if (is_power_change_pending) { - // Invert battery power status here instead of using the value returned by - // the PowerObserver since there may be a pending OnPowerStateChange(). - is_on_battery_power_ = !is_on_battery_power_; + // Always send finalize, even if we don't currently have any data, it's + // harmless to send since nothing will be logged if we've already finalized. + base_component_->Finalize(&keys_to_finalize); + recorder_->FinalizeWatchTime({}); - start_timestamp_for_power_ = end_timestamp_for_power_; - end_timestamp_for_power_ = kNoTimestamp; - } + // Stop the timer if this is supposed to be our last tick. + underflow_count_ = 0; + reporting_timer_.Stop(); +} - if (is_controls_change_pending) { - has_native_controls_ = !has_native_controls_; +#define NORMAL_KEY(key) \ + ((properties_->has_video && properties_->has_audio) \ + ? (is_background_ ? WatchTimeKey::kAudioVideoBackground##key \ + : (is_muted_ ? WatchTimeKey::kAudioVideoMuted##key \ + : WatchTimeKey::kAudioVideo##key)) \ + : properties_->has_video \ + ? (is_background_ ? WatchTimeKey::kVideoBackground##key \ + : WatchTimeKey::kVideo##key) \ + : (is_background_ ? WatchTimeKey::kAudioBackground##key \ + : WatchTimeKey::kAudio##key)) + +std::unique_ptr<WatchTimeComponent<bool>> +WatchTimeReporter::CreateBaseComponent() { + std::vector<WatchTimeKey> keys_to_finalize; + keys_to_finalize.emplace_back(NORMAL_KEY(All)); + if (properties_->is_mse) + keys_to_finalize.emplace_back(NORMAL_KEY(Mse)); + else + keys_to_finalize.emplace_back(NORMAL_KEY(Src)); + + if (properties_->is_eme) + keys_to_finalize.emplace_back(NORMAL_KEY(Eme)); + + if (properties_->is_embedded_media_experience) + keys_to_finalize.emplace_back(NORMAL_KEY(EmbeddedExperience)); + + return std::make_unique<WatchTimeComponent<bool>>( + false, std::move(keys_to_finalize), + WatchTimeComponent<bool>::ValueToKeyCB(), get_media_time_cb_, + recorder_.get()); +} - start_timestamp_for_controls_ = end_timestamp_for_controls_; - end_timestamp_for_controls_ = kNoTimestamp; - } +std::unique_ptr<WatchTimeComponent<bool>> +WatchTimeReporter::CreatePowerComponent() { + std::vector<WatchTimeKey> keys_to_finalize{NORMAL_KEY(Battery), + NORMAL_KEY(Ac)}; - if (is_display_type_change_pending) { - display_type_for_recording_ = display_type_; + return std::make_unique<WatchTimeComponent<bool>>( + IsOnBatteryPower(), std::move(keys_to_finalize), + base::BindRepeating(&WatchTimeReporter::GetPowerKey, + base::Unretained(this)), + get_media_time_cb_, recorder_.get()); +} - start_timestamp_for_display_type_ = end_timestamp_for_display_type_; - end_timestamp_for_display_type_ = kNoTimestamp; - } +WatchTimeKey WatchTimeReporter::GetPowerKey(bool is_on_battery_power) { + return is_on_battery_power ? NORMAL_KEY(Battery) : NORMAL_KEY(Ac); +} +#undef NORMAL_KEY - // Stop the timer if this is supposed to be our last tick. - if (is_finalizing) { - end_timestamp_ = kNoTimestamp; - underflow_count_ = 0; - reporting_timer_.Stop(); - } +#define FOREGROUND_KEY(key) \ + ((properties_->has_video && properties_->has_audio) \ + ? (is_muted_ ? WatchTimeKey::kAudioVideoMuted##key \ + : WatchTimeKey::kAudioVideo##key) \ + : properties_->has_audio ? WatchTimeKey::kAudio##key \ + : WatchTimeKey::kVideo##key) + +std::unique_ptr<WatchTimeComponent<bool>> +WatchTimeReporter::CreateControlsComponent() { + DCHECK(!is_background_); + + std::vector<WatchTimeKey> keys_to_finalize{FOREGROUND_KEY(NativeControlsOn), + FOREGROUND_KEY(NativeControlsOff)}; + + return std::make_unique<WatchTimeComponent<bool>>( + false, std::move(keys_to_finalize), + base::BindRepeating(&WatchTimeReporter::GetControlsKey, + base::Unretained(this)), + get_media_time_cb_, recorder_.get()); } -void WatchTimeReporter::OnDisplayTypeChanged( - blink::WebMediaPlayer::DisplayType display_type) { - if (muted_reporter_) - muted_reporter_->OnDisplayTypeChanged(display_type); +WatchTimeKey WatchTimeReporter::GetControlsKey(bool has_native_controls) { + return has_native_controls ? FOREGROUND_KEY(NativeControlsOn) + : FOREGROUND_KEY(NativeControlsOff); +} - display_type_ = display_type; +#undef FOREGROUND_KEY - if (!reporting_timer_.IsRunning()) - return; +#define DISPLAY_TYPE_KEY(key) \ + (properties_->has_audio ? (is_muted_ ? WatchTimeKey::kAudioVideoMuted##key \ + : WatchTimeKey::kAudioVideo##key) \ + : WatchTimeKey::kVideo##key) - if (display_type_for_recording_ == display_type_) { - end_timestamp_for_display_type_ = kNoTimestamp; - return; - } +std::unique_ptr<WatchTimeComponent<WatchTimeReporter::DisplayType>> +WatchTimeReporter::CreateDisplayTypeComponent() { + DCHECK(properties_->has_video); + DCHECK(!is_background_); - end_timestamp_for_display_type_ = get_media_time_cb_.Run(); - reporting_timer_.Start(FROM_HERE, reporting_interval_, this, - &WatchTimeReporter::UpdateWatchTime); + std::vector<WatchTimeKey> keys_to_finalize{ + DISPLAY_TYPE_KEY(DisplayInline), DISPLAY_TYPE_KEY(DisplayFullscreen), + DISPLAY_TYPE_KEY(DisplayPictureInPicture)}; + + return std::make_unique<WatchTimeComponent<DisplayType>>( + DisplayType::kInline, std::move(keys_to_finalize), + base::BindRepeating(&WatchTimeReporter::GetDisplayTypeKey, + base::Unretained(this)), + get_media_time_cb_, recorder_.get()); } +WatchTimeKey WatchTimeReporter::GetDisplayTypeKey(DisplayType display_type) { + switch (display_type) { + case DisplayType::kInline: + return DISPLAY_TYPE_KEY(DisplayInline); + case DisplayType::kFullscreen: + return DISPLAY_TYPE_KEY(DisplayFullscreen); + case DisplayType::kPictureInPicture: + return DISPLAY_TYPE_KEY(DisplayPictureInPicture); + } +} + +#undef DISPLAY_TYPE_KEY + } // namespace media |