summaryrefslogtreecommitdiff
path: root/chromium/media/blink/watch_time_reporter.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/blink/watch_time_reporter.cc')
-rw-r--r--chromium/media/blink/watch_time_reporter.cc600
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