Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/blink/watch_time_reporter.h" | |
| 6 | |
| 7 #include "base/metrics/histogram_macros.h" | |
| 8 #include "base/power_monitor/power_monitor.h" | |
| 9 | |
| 10 namespace media { | |
| 11 | |
| 12 // The minimum amount of media playback which can elapse before we'll report | |
| 13 // watch time metrics for a playback. | |
| 14 constexpr base::TimeDelta kMinimumElapsedWatchTime = | |
| 15 base::TimeDelta::FromSeconds(7); | |
| 16 | |
| 17 // The minimum width and height of videos to report watch time metrics for. | |
| 18 constexpr gfx::Size kMinimumVideoSize = gfx::Size(200, 200); | |
| 19 | |
| 20 // Histogram names exported for testing. | |
| 21 const char WatchTimeReporter::kHistogramAudioVideoAll[] = | |
| 22 "Media.WatchTime.AudioVideo.All"; | |
| 23 const char WatchTimeReporter::kHistogramAudioVideoMse[] = | |
| 24 "Media.WatchTime.AudioVideo.MSE"; | |
| 25 const char WatchTimeReporter::kHistogramAudioVideoEme[] = | |
| 26 "Media.WatchTime.AudioVideo.EME"; | |
| 27 const char WatchTimeReporter::kHistogramAudioVideoSrc[] = | |
| 28 "Media.WatchTime.AudioVideo.SRC"; | |
| 29 const char WatchTimeReporter::kHistogramAudioVideoBattery[] = | |
| 30 "Media.WatchTime.AudioVideo.Battery"; | |
| 31 const char WatchTimeReporter::kHistogramAudioVideoAc[] = | |
| 32 "Media.WatchTime.AudioVideo.AC"; | |
| 33 | |
| 34 WatchTimeReporter::WatchTimeReporter(bool has_audio, | |
| 35 bool has_video, | |
| 36 bool is_mse, | |
| 37 bool is_encrypted, | |
| 38 const gfx::Size& initial_video_size, | |
| 39 const GetMediaTimeCB& get_media_time_cb) | |
| 40 : has_audio_(has_audio), | |
| 41 has_video_(has_video), | |
| 42 is_mse_(is_mse), | |
| 43 is_encrypted_(is_encrypted), | |
| 44 initial_video_size_(initial_video_size), | |
| 45 get_media_time_cb_(get_media_time_cb) { | |
| 46 DCHECK(!get_media_time_cb_.is_null()); | |
| 47 DCHECK(has_audio_ || has_video_); | |
| 48 if (has_video_) | |
| 49 DCHECK(!initial_video_size_.IsEmpty()); | |
| 50 base::PowerMonitor::Get()->AddObserver(this); | |
| 51 } | |
| 52 | |
| 53 WatchTimeReporter::~WatchTimeReporter() { | |
| 54 // If the timer is still running, finalize immediately, this is our last | |
| 55 // chance to capture metrics. | |
| 56 if (reporting_timer_.IsRunning()) | |
| 57 FinalizeWatchTime(FinalizeTime::IMMEDIATELY); | |
| 58 | |
| 59 base::PowerMonitor::Get()->RemoveObserver(this); | |
| 60 } | |
| 61 | |
| 62 void WatchTimeReporter::OnPlaying() { | |
| 63 is_playing_ = true; | |
| 64 StartReportingTimer(get_media_time_cb_.Run()); | |
| 65 } | |
| 66 | |
| 67 void WatchTimeReporter::OnPaused() { | |
| 68 is_playing_ = false; | |
| 69 FinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); | |
| 70 } | |
| 71 | |
| 72 void WatchTimeReporter::OnSeeking(base::TimeDelta seek_timestamp) { | |
| 73 // Seek is a special case that does not have hysteresis, when this is called | |
| 74 // the seek is imminent, so finalize the previous playback immediately. | |
| 75 if (reporting_timer_.IsRunning()) { | |
| 76 // Don't trample an existing end timestamp. | |
| 77 if (end_timestamp_ != kNoTimestamp) | |
| 78 end_timestamp_ = get_media_time_cb_.Run(); | |
| 79 UpdateWatchTime(); | |
| 80 } | |
| 81 | |
| 82 // Start the reporting timer at the target seek time; no watch time will be | |
| 83 // reported until |kMinimumElapsedWatchTime| after the seek time. | |
| 84 StartReportingTimer(seek_timestamp); | |
| 85 } | |
| 86 | |
| 87 void WatchTimeReporter::OnVolumeChange(double volume) { | |
| 88 const double old_volume = volume_; | |
| 89 volume_ = volume; | |
| 90 | |
| 91 // We're only interesting in transitions in and out of the muted state. | |
| 92 if (!old_volume && volume) | |
| 93 StartReportingTimer(get_media_time_cb_.Run()); | |
| 94 else if (old_volume && !volume_) | |
| 95 FinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); | |
| 96 } | |
| 97 | |
| 98 void WatchTimeReporter::OnShown() { | |
| 99 is_visible_ = true; | |
| 100 StartReportingTimer(get_media_time_cb_.Run()); | |
| 101 } | |
| 102 | |
| 103 void WatchTimeReporter::OnHidden() { | |
| 104 is_visible_ = false; | |
| 105 FinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); | |
| 106 } | |
| 107 | |
| 108 void WatchTimeReporter::OnPowerStateChange(bool on_battery_power) { | |
| 109 if (!reporting_timer_.IsRunning()) | |
| 110 return; | |
| 111 | |
| 112 // Defer changing |is_on_battery_power_| until the next watch time report to | |
| 113 // avoid momentary power changes from affecting the results. | |
| 114 if (is_on_battery_power_ != on_battery_power) { | |
| 115 end_timestamp_for_power_ = get_media_time_cb_.Run(); | |
| 116 | |
| 117 // Restart the reporting timer so the full hysteresis is afforded. | |
| 118 reporting_timer_.Start(FROM_HERE, reporting_interval_, this, | |
| 119 &WatchTimeReporter::UpdateWatchTime); | |
| 120 return; | |
| 121 } | |
| 122 | |
| 123 end_timestamp_for_power_ = kNoTimestamp; | |
| 124 } | |
| 125 | |
| 126 bool WatchTimeReporter::ShouldReportWatchTime() { | |
| 127 // Only report watch time for media of sufficient size with both audio and | |
| 128 // video tracks present. | |
| 129 return has_audio_ && has_video_ && | |
| 130 initial_video_size_.height() >= kMinimumVideoSize.height() && | |
| 131 initial_video_size_.width() >= kMinimumVideoSize.width(); | |
| 132 } | |
| 133 | |
| 134 void WatchTimeReporter::StartReportingTimer(base::TimeDelta start_timestamp) { | |
| 135 // Don't start the timer if any of our state indicates we shouldn't; this | |
| 136 // check is important since the various event handlers do not have to care | |
| 137 // about the state of other events. | |
| 138 if (!ShouldReportWatchTime() || !is_playing_ || !volume_ || !is_visible_) { | |
| 139 // If we reach this point the timer should already have been stopped or | |
| 140 // there is a pending finalize in flight. | |
| 141 DCHECK(!reporting_timer_.IsRunning() || end_timestamp_ != kNoTimestamp); | |
| 142 return; | |
| 143 } | |
| 144 | |
| 145 // If we haven't finalized the last watch time metrics yet, count this | |
| 146 // playback as a continuation of the previous metrics. | |
| 147 if (end_timestamp_ != kNoTimestamp) { | |
| 148 DCHECK(reporting_timer_.IsRunning()); | |
| 149 end_timestamp_ = kNoTimestamp; | |
| 150 return; | |
| 151 } | |
| 152 | |
| 153 // Don't restart the timer if it's already running. | |
| 154 if (reporting_timer_.IsRunning()) | |
| 155 return; | |
| 156 | |
| 157 last_media_timestamp_ = end_timestamp_for_power_ = kNoTimestamp; | |
| 158 is_on_battery_power_ = base::PowerMonitor::Get()->IsOnBatteryPower(); | |
| 159 start_timestamp_ = start_timestamp_for_power_ = start_timestamp; | |
| 160 reporting_timer_.Start(FROM_HERE, reporting_interval_, this, | |
| 161 &WatchTimeReporter::UpdateWatchTime); | |
| 162 } | |
| 163 | |
| 164 void WatchTimeReporter::FinalizeWatchTime(FinalizeTime finalize_time) { | |
| 165 // Don't finalize if the timer is already stopped. | |
| 166 if (!reporting_timer_.IsRunning()) | |
| 167 return; | |
| 168 | |
| 169 // Don't trample an existing finalize; the first takes precedence. | |
| 170 if (end_timestamp_ == kNoTimestamp) | |
| 171 end_timestamp_ = get_media_time_cb_.Run(); | |
| 172 | |
| 173 if (finalize_time == FinalizeTime::IMMEDIATELY) { | |
| 174 UpdateWatchTime(); | |
| 175 return; | |
| 176 } | |
| 177 | |
| 178 // Always restart the timer when finalizing, so that we allow for the full | |
| 179 // length of |kReportingInterval| to elapse for hysteresis purposes. | |
| 180 DCHECK_EQ(finalize_time, FinalizeTime::ON_NEXT_UPDATE); | |
| 181 reporting_timer_.Start(FROM_HERE, reporting_interval_, this, | |
| 182 &WatchTimeReporter::UpdateWatchTime); | |
| 183 } | |
| 184 | |
| 185 void WatchTimeReporter::UpdateWatchTime() { | |
| 186 DCHECK(ShouldReportWatchTime()); | |
| 187 | |
| 188 const bool is_finalizing = end_timestamp_ != kNoTimestamp; | |
| 189 const bool is_power_change_pending = end_timestamp_for_power_ != kNoTimestamp; | |
| 190 | |
| 191 // If we're finalizing the histogram, use the media time value at the time of | |
| 192 // finalization. | |
| 193 const base::TimeDelta current_timestamp = | |
| 194 is_finalizing ? end_timestamp_ : get_media_time_cb_.Run(); | |
| 195 const base::TimeDelta elapsed = current_timestamp - start_timestamp_; | |
| 196 | |
| 197 // Only report watch time after some minimum amount has elapsed. Don't update | |
| 198 // watch time if media time hasn't changed since the last run; this may occur | |
| 199 // if a seek is taking some time to complete or the playback is stalled for | |
| 200 // some reason. | |
| 201 if (elapsed >= kMinimumElapsedWatchTime && | |
| 202 last_media_timestamp_ != current_timestamp) { | |
| 203 last_media_timestamp_ = current_timestamp; | |
| 204 | |
| 205 UMA_HISTOGRAM_LONG_TIMES(kHistogramAudioVideoAll, elapsed); | |
|
sandersd (OOO until July 31)
2016/08/04 23:06:26
It looks like this will report repeatedly, with a
DaleCurtis
2016/08/04 23:34:21
No coalescing, we end up with categories like the
sandersd (OOO until July 31)
2016/08/04 23:57:18
holte@: It's not obvious that we can get the above
| |
| 206 if (is_mse_) | |
| 207 UMA_HISTOGRAM_LONG_TIMES(kHistogramAudioVideoMse, elapsed); | |
| 208 else | |
| 209 UMA_HISTOGRAM_LONG_TIMES(kHistogramAudioVideoSrc, elapsed); | |
| 210 if (is_encrypted_) | |
| 211 UMA_HISTOGRAM_LONG_TIMES(kHistogramAudioVideoEme, elapsed); | |
| 212 | |
| 213 // Record watch time using the last known value for |is_on_battery_power_|; | |
| 214 // if there's a |pending_power_change_| use that to accurately finalize the | |
| 215 // last bits of time in the previous bucket. | |
| 216 const base::TimeDelta elapsed_power = | |
| 217 (is_power_change_pending ? end_timestamp_for_power_ | |
| 218 : current_timestamp) - | |
| 219 start_timestamp_for_power_; | |
| 220 | |
| 221 // Again, only update watch time if enough time has elapsed; we need to | |
| 222 // recheck the elapsed time here since the power source can change anytime. | |
| 223 if (elapsed_power >= kMinimumElapsedWatchTime) { | |
| 224 if (is_on_battery_power_) { | |
| 225 UMA_HISTOGRAM_LONG_TIMES(kHistogramAudioVideoBattery, elapsed_power); | |
| 226 } else { | |
| 227 UMA_HISTOGRAM_LONG_TIMES(kHistogramAudioVideoAc, elapsed_power); | |
| 228 } | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 if (is_power_change_pending) { | |
| 233 // Invert battery power status here instead of using the value returned by | |
| 234 // the PowerObserver since there may be a pending OnPowerStateChange(). | |
| 235 is_on_battery_power_ = !is_on_battery_power_; | |
| 236 | |
| 237 start_timestamp_for_power_ = end_timestamp_for_power_; | |
| 238 end_timestamp_for_power_ = kNoTimestamp; | |
| 239 } | |
| 240 | |
| 241 // Stop the timer if this is supposed to be our last tick. | |
| 242 if (is_finalizing) { | |
| 243 end_timestamp_ = kNoTimestamp; | |
| 244 reporting_timer_.Stop(); | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 } // namespace media | |
| OLD | NEW |