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