| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/blink/watch_time_reporter.h" | 5 #include "media/blink/watch_time_reporter.h" |
| 6 | 6 |
| 7 #include "base/power_monitor/power_monitor.h" | 7 #include "base/power_monitor/power_monitor.h" |
| 8 | 8 |
| 9 namespace media { | 9 namespace media { |
| 10 | 10 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 } | 23 } |
| 24 | 24 |
| 25 WatchTimeReporter::WatchTimeReporter(bool has_audio, | 25 WatchTimeReporter::WatchTimeReporter(bool has_audio, |
| 26 bool has_video, | 26 bool has_video, |
| 27 bool is_mse, | 27 bool is_mse, |
| 28 bool is_encrypted, | 28 bool is_encrypted, |
| 29 bool is_embedded_media_experience_enabled, | 29 bool is_embedded_media_experience_enabled, |
| 30 scoped_refptr<MediaLog> media_log, | 30 scoped_refptr<MediaLog> media_log, |
| 31 const gfx::Size& initial_video_size, | 31 const gfx::Size& initial_video_size, |
| 32 const GetMediaTimeCB& get_media_time_cb) | 32 const GetMediaTimeCB& get_media_time_cb) |
| 33 : WatchTimeReporter(has_audio, |
| 34 has_video, |
| 35 is_mse, |
| 36 is_encrypted, |
| 37 is_embedded_media_experience_enabled, |
| 38 std::move(media_log), |
| 39 initial_video_size, |
| 40 get_media_time_cb, |
| 41 false) {} |
| 42 |
| 43 WatchTimeReporter::WatchTimeReporter(bool has_audio, |
| 44 bool has_video, |
| 45 bool is_mse, |
| 46 bool is_encrypted, |
| 47 bool is_embedded_media_experience_enabled, |
| 48 scoped_refptr<MediaLog> media_log, |
| 49 const gfx::Size& initial_video_size, |
| 50 const GetMediaTimeCB& get_media_time_cb, |
| 51 bool is_background) |
| 33 : has_audio_(has_audio), | 52 : has_audio_(has_audio), |
| 34 has_video_(has_video), | 53 has_video_(has_video), |
| 35 is_mse_(is_mse), | 54 is_mse_(is_mse), |
| 36 is_encrypted_(is_encrypted), | 55 is_encrypted_(is_encrypted), |
| 37 is_embedded_media_experience_enabled_( | 56 is_embedded_media_experience_enabled_( |
| 38 is_embedded_media_experience_enabled), | 57 is_embedded_media_experience_enabled), |
| 39 media_log_(std::move(media_log)), | 58 media_log_(std::move(media_log)), |
| 40 initial_video_size_(initial_video_size), | 59 initial_video_size_(initial_video_size), |
| 41 get_media_time_cb_(get_media_time_cb) { | 60 get_media_time_cb_(get_media_time_cb), |
| 61 is_background_(is_background) { |
| 42 DCHECK(!get_media_time_cb_.is_null()); | 62 DCHECK(!get_media_time_cb_.is_null()); |
| 43 DCHECK(has_audio_ || has_video_); | 63 DCHECK(has_audio_ || has_video_); |
| 44 | 64 |
| 45 if (base::PowerMonitor* pm = base::PowerMonitor::Get()) | 65 if (base::PowerMonitor* pm = base::PowerMonitor::Get()) |
| 46 pm->AddObserver(this); | 66 pm->AddObserver(this); |
| 67 |
| 68 if (is_background_) { |
| 69 DCHECK(has_audio_); |
| 70 DCHECK(!has_video_); |
| 71 return; |
| 72 } |
| 73 |
| 74 // Background watch time is reported by creating an audio only watch time |
| 75 // reporter which receives play when hidden and pause when shown. This avoids |
| 76 // unnecessary complexity inside the UpdateWatchTime() for handling this case. |
| 77 if (has_video_ && has_audio_ && ShouldReportWatchTime()) { |
| 78 background_reporter_.reset( |
| 79 new WatchTimeReporter(has_audio_, false, is_mse_, is_encrypted_, |
| 80 is_embedded_media_experience_enabled_, media_log_, |
| 81 initial_video_size_, get_media_time_cb_, true)); |
| 82 } |
| 47 } | 83 } |
| 48 | 84 |
| 49 WatchTimeReporter::~WatchTimeReporter() { | 85 WatchTimeReporter::~WatchTimeReporter() { |
| 86 background_reporter_.reset(); |
| 87 |
| 50 // If the timer is still running, finalize immediately, this is our last | 88 // If the timer is still running, finalize immediately, this is our last |
| 51 // chance to capture metrics. | 89 // chance to capture metrics. |
| 52 if (reporting_timer_.IsRunning()) | 90 if (reporting_timer_.IsRunning()) |
| 53 MaybeFinalizeWatchTime(FinalizeTime::IMMEDIATELY); | 91 MaybeFinalizeWatchTime(FinalizeTime::IMMEDIATELY); |
| 54 | 92 |
| 55 if (base::PowerMonitor* pm = base::PowerMonitor::Get()) | 93 if (base::PowerMonitor* pm = base::PowerMonitor::Get()) |
| 56 pm->RemoveObserver(this); | 94 pm->RemoveObserver(this); |
| 57 } | 95 } |
| 58 | 96 |
| 59 void WatchTimeReporter::OnPlaying() { | 97 void WatchTimeReporter::OnPlaying() { |
| 98 if (background_reporter_ && !is_visible_) |
| 99 background_reporter_->OnPlaying(); |
| 100 |
| 60 is_playing_ = true; | 101 is_playing_ = true; |
| 61 MaybeStartReportingTimer(get_media_time_cb_.Run()); | 102 MaybeStartReportingTimer(get_media_time_cb_.Run()); |
| 62 } | 103 } |
| 63 | 104 |
| 64 void WatchTimeReporter::OnPaused() { | 105 void WatchTimeReporter::OnPaused() { |
| 106 if (background_reporter_) |
| 107 background_reporter_->OnPaused(); |
| 108 |
| 65 is_playing_ = false; | 109 is_playing_ = false; |
| 66 MaybeFinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); | 110 MaybeFinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); |
| 67 } | 111 } |
| 68 | 112 |
| 69 void WatchTimeReporter::OnSeeking() { | 113 void WatchTimeReporter::OnSeeking() { |
| 114 if (background_reporter_) |
| 115 background_reporter_->OnSeeking(); |
| 116 |
| 70 if (!reporting_timer_.IsRunning()) | 117 if (!reporting_timer_.IsRunning()) |
| 71 return; | 118 return; |
| 72 | 119 |
| 73 // Seek is a special case that does not have hysteresis, when this is called | 120 // 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. | 121 // the seek is imminent, so finalize the previous playback immediately. |
| 75 | 122 |
| 76 // Don't trample an existing end timestamp. | 123 // Don't trample an existing end timestamp. |
| 77 if (end_timestamp_ == kNoTimestamp) | 124 if (end_timestamp_ == kNoTimestamp) |
| 78 end_timestamp_ = get_media_time_cb_.Run(); | 125 end_timestamp_ = get_media_time_cb_.Run(); |
| 79 UpdateWatchTime(); | 126 UpdateWatchTime(); |
| 80 } | 127 } |
| 81 | 128 |
| 82 void WatchTimeReporter::OnVolumeChange(double volume) { | 129 void WatchTimeReporter::OnVolumeChange(double volume) { |
| 130 if (background_reporter_) |
| 131 background_reporter_->OnVolumeChange(volume); |
| 132 |
| 83 const double old_volume = volume_; | 133 const double old_volume = volume_; |
| 84 volume_ = volume; | 134 volume_ = volume; |
| 85 | 135 |
| 86 // We're only interesting in transitions in and out of the muted state. | 136 // We're only interesting in transitions in and out of the muted state. |
| 87 if (!old_volume && volume) | 137 if (!old_volume && volume) |
| 88 MaybeStartReportingTimer(get_media_time_cb_.Run()); | 138 MaybeStartReportingTimer(get_media_time_cb_.Run()); |
| 89 else if (old_volume && !volume_) | 139 else if (old_volume && !volume_) |
| 90 MaybeFinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); | 140 MaybeFinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); |
| 91 } | 141 } |
| 92 | 142 |
| 93 void WatchTimeReporter::OnShown() { | 143 void WatchTimeReporter::OnShown() { |
| 144 if (background_reporter_) |
| 145 background_reporter_->OnPaused(); |
| 146 |
| 94 if (!has_video_) | 147 if (!has_video_) |
| 95 return; | 148 return; |
| 96 | 149 |
| 97 is_visible_ = true; | 150 is_visible_ = true; |
| 98 MaybeStartReportingTimer(get_media_time_cb_.Run()); | 151 MaybeStartReportingTimer(get_media_time_cb_.Run()); |
| 99 } | 152 } |
| 100 | 153 |
| 101 void WatchTimeReporter::OnHidden() { | 154 void WatchTimeReporter::OnHidden() { |
| 155 if (background_reporter_ && is_playing_) |
| 156 background_reporter_->OnPlaying(); |
| 157 |
| 102 if (!has_video_) | 158 if (!has_video_) |
| 103 return; | 159 return; |
| 104 | 160 |
| 105 is_visible_ = false; | 161 is_visible_ = false; |
| 106 MaybeFinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); | 162 MaybeFinalizeWatchTime(FinalizeTime::ON_NEXT_UPDATE); |
| 107 } | 163 } |
| 108 | 164 |
| 109 void WatchTimeReporter::OnPowerStateChange(bool on_battery_power) { | 165 void WatchTimeReporter::OnPowerStateChange(bool on_battery_power) { |
| 110 if (!reporting_timer_.IsRunning()) | 166 if (!reporting_timer_.IsRunning()) |
| 111 return; | 167 return; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 150 if (end_timestamp_ != kNoTimestamp) { | 206 if (end_timestamp_ != kNoTimestamp) { |
| 151 DCHECK(reporting_timer_.IsRunning()); | 207 DCHECK(reporting_timer_.IsRunning()); |
| 152 end_timestamp_ = kNoTimestamp; | 208 end_timestamp_ = kNoTimestamp; |
| 153 return; | 209 return; |
| 154 } | 210 } |
| 155 | 211 |
| 156 // Don't restart the timer if it's already running. | 212 // Don't restart the timer if it's already running. |
| 157 if (reporting_timer_.IsRunning()) | 213 if (reporting_timer_.IsRunning()) |
| 158 return; | 214 return; |
| 159 | 215 |
| 160 last_media_timestamp_ = end_timestamp_for_power_ = kNoTimestamp; | 216 last_media_timestamp_ = last_media_power_timestamp_ = |
| 217 end_timestamp_for_power_ = kNoTimestamp; |
| 161 is_on_battery_power_ = IsOnBatteryPower(); | 218 is_on_battery_power_ = IsOnBatteryPower(); |
| 162 start_timestamp_ = start_timestamp_for_power_ = start_timestamp; | 219 start_timestamp_ = start_timestamp_for_power_ = start_timestamp; |
| 163 reporting_timer_.Start(FROM_HERE, reporting_interval_, this, | 220 reporting_timer_.Start(FROM_HERE, reporting_interval_, this, |
| 164 &WatchTimeReporter::UpdateWatchTime); | 221 &WatchTimeReporter::UpdateWatchTime); |
| 165 } | 222 } |
| 166 | 223 |
| 167 void WatchTimeReporter::MaybeFinalizeWatchTime(FinalizeTime finalize_time) { | 224 void WatchTimeReporter::MaybeFinalizeWatchTime(FinalizeTime finalize_time) { |
| 168 // Don't finalize if the timer is already stopped. | 225 // Don't finalize if the timer is already stopped. |
| 169 if (!reporting_timer_.IsRunning()) | 226 if (!reporting_timer_.IsRunning()) |
| 170 return; | 227 return; |
| (...skipping 19 matching lines...) Expand all Loading... |
| 190 | 247 |
| 191 const bool is_finalizing = end_timestamp_ != kNoTimestamp; | 248 const bool is_finalizing = end_timestamp_ != kNoTimestamp; |
| 192 const bool is_power_change_pending = end_timestamp_for_power_ != kNoTimestamp; | 249 const bool is_power_change_pending = end_timestamp_for_power_ != kNoTimestamp; |
| 193 | 250 |
| 194 // If we're finalizing the log, use the media time value at the time of | 251 // If we're finalizing the log, use the media time value at the time of |
| 195 // finalization. | 252 // finalization. |
| 196 const base::TimeDelta current_timestamp = | 253 const base::TimeDelta current_timestamp = |
| 197 is_finalizing ? end_timestamp_ : get_media_time_cb_.Run(); | 254 is_finalizing ? end_timestamp_ : get_media_time_cb_.Run(); |
| 198 const base::TimeDelta elapsed = current_timestamp - start_timestamp_; | 255 const base::TimeDelta elapsed = current_timestamp - start_timestamp_; |
| 199 | 256 |
| 257 std::unique_ptr<MediaLogEvent> log_event = |
| 258 media_log_->CreateEvent(MediaLogEvent::Type::WATCH_TIME_UPDATE); |
| 259 |
| 260 #define RECORD_WATCH_TIME(key, value) \ |
| 261 do { \ |
| 262 log_event->params.SetDoubleWithoutPathExpansion( \ |
| 263 has_video_ \ |
| 264 ? MediaLog::kWatchTimeAudioVideo##key \ |
| 265 : (is_background_ ? MediaLog::kWatchTimeAudioVideoBackground##key \ |
| 266 : MediaLog::kWatchTimeAudio##key), \ |
| 267 value.InSecondsF()); \ |
| 268 } while (0) |
| 269 |
| 200 // Only report watch time after some minimum amount has elapsed. Don't update | 270 // Only report watch time after some minimum amount has elapsed. Don't update |
| 201 // watch time if media time hasn't changed since the last run; this may occur | 271 // watch time if media time hasn't changed since the last run; this may occur |
| 202 // if a seek is taking some time to complete or the playback is stalled for | 272 // if a seek is taking some time to complete or the playback is stalled for |
| 203 // some reason. | 273 // some reason. |
| 204 if (elapsed >= kMinimumElapsedWatchTime && | 274 if (last_media_timestamp_ != current_timestamp) { |
| 205 last_media_timestamp_ != current_timestamp) { | |
| 206 last_media_timestamp_ = current_timestamp; | 275 last_media_timestamp_ = current_timestamp; |
| 207 | 276 |
| 208 std::unique_ptr<MediaLogEvent> log_event = | 277 if (elapsed >= kMinimumElapsedWatchTime) { |
| 209 media_log_->CreateEvent(MediaLogEvent::Type::WATCH_TIME_UPDATE); | 278 RECORD_WATCH_TIME(All, elapsed); |
| 279 if (is_mse_) |
| 280 RECORD_WATCH_TIME(Mse, elapsed); |
| 281 else |
| 282 RECORD_WATCH_TIME(Src, elapsed); |
| 210 | 283 |
| 211 log_event->params.SetDoubleWithoutPathExpansion( | 284 if (is_encrypted_) |
| 212 has_video_ ? MediaLog::kWatchTimeAudioVideoAll | 285 RECORD_WATCH_TIME(Eme, elapsed); |
| 213 : MediaLog::kWatchTimeAudioAll, | 286 |
| 214 elapsed.InSecondsF()); | 287 if (is_embedded_media_experience_enabled_) |
| 215 if (is_mse_) { | 288 RECORD_WATCH_TIME(EmbeddedExperience, elapsed); |
| 216 log_event->params.SetDoubleWithoutPathExpansion( | |
| 217 has_video_ ? MediaLog::kWatchTimeAudioVideoMse | |
| 218 : MediaLog::kWatchTimeAudioMse, | |
| 219 elapsed.InSecondsF()); | |
| 220 } else { | |
| 221 log_event->params.SetDoubleWithoutPathExpansion( | |
| 222 has_video_ ? MediaLog::kWatchTimeAudioVideoSrc | |
| 223 : MediaLog::kWatchTimeAudioSrc, | |
| 224 elapsed.InSecondsF()); | |
| 225 } | 289 } |
| 226 if (is_encrypted_) { | 290 } |
| 227 log_event->params.SetDoubleWithoutPathExpansion( | |
| 228 has_video_ ? MediaLog::kWatchTimeAudioVideoEme | |
| 229 : MediaLog::kWatchTimeAudioEme, | |
| 230 elapsed.InSecondsF()); | |
| 231 } | |
| 232 | 291 |
| 233 if (is_embedded_media_experience_enabled_) { | 292 if (last_media_power_timestamp_ != current_timestamp) { |
| 234 log_event->params.SetDoubleWithoutPathExpansion( | 293 // We need a separate |last_media_power_timestamp_| since we don't always |
| 235 has_video_ ? MediaLog::kWatchTimeAudioVideoEmbeddedExperience | 294 // base the last watch time calculation on the current timestamp. |
| 236 : MediaLog::kWatchTimeAudioEmbeddedExperience, | 295 last_media_power_timestamp_ = |
| 237 elapsed.InSecondsF()); | 296 is_power_change_pending ? end_timestamp_for_power_ : current_timestamp; |
| 238 } | |
| 239 | 297 |
| 240 // Record watch time using the last known value for |is_on_battery_power_|; | 298 // Record watch time using the last known value for |is_on_battery_power_|; |
| 241 // if there's a |pending_power_change_| use that to accurately finalize the | 299 // if there's a |pending_power_change_| use that to accurately finalize the |
| 242 // last bits of time in the previous bucket. | 300 // last bits of time in the previous bucket. |
| 243 const base::TimeDelta elapsed_power = | 301 const base::TimeDelta elapsed_power = |
| 244 (is_power_change_pending ? end_timestamp_for_power_ | 302 last_media_power_timestamp_ - start_timestamp_for_power_; |
| 245 : current_timestamp) - | |
| 246 start_timestamp_for_power_; | |
| 247 | 303 |
| 248 // Again, only update watch time if enough time has elapsed; we need to | 304 // Again, only update watch time if enough time has elapsed; we need to |
| 249 // recheck the elapsed time here since the power source can change anytime. | 305 // recheck the elapsed time here since the power source can change anytime. |
| 250 if (elapsed_power >= kMinimumElapsedWatchTime) { | 306 if (elapsed_power >= kMinimumElapsedWatchTime) { |
| 251 if (is_on_battery_power_) { | 307 if (is_on_battery_power_) |
| 252 log_event->params.SetDoubleWithoutPathExpansion( | 308 RECORD_WATCH_TIME(Battery, elapsed_power); |
| 253 has_video_ ? MediaLog::kWatchTimeAudioVideoBattery | 309 else |
| 254 : MediaLog::kWatchTimeAudioBattery, | 310 RECORD_WATCH_TIME(Ac, elapsed_power); |
| 255 elapsed_power.InSecondsF()); | |
| 256 } else { | |
| 257 log_event->params.SetDoubleWithoutPathExpansion( | |
| 258 has_video_ ? MediaLog::kWatchTimeAudioVideoAc | |
| 259 : MediaLog::kWatchTimeAudioAc, | |
| 260 elapsed_power.InSecondsF()); | |
| 261 } | |
| 262 } | 311 } |
| 312 } |
| 313 #undef RECORD_WATCH_TIME |
| 263 | 314 |
| 264 if (is_finalizing) | 315 // Always send finalize, even if we don't currently have any data, it's |
| 265 log_event->params.SetBoolean(MediaLog::kWatchTimeFinalize, true); | 316 // harmless to send since nothing will be logged if we've already finalized. |
| 266 else if (is_power_change_pending) | 317 if (is_finalizing) |
| 267 log_event->params.SetBoolean(MediaLog::kWatchTimeFinalizePower, true); | 318 log_event->params.SetBoolean(MediaLog::kWatchTimeFinalize, true); |
| 319 else if (is_power_change_pending) |
| 320 log_event->params.SetBoolean(MediaLog::kWatchTimeFinalizePower, true); |
| 268 | 321 |
| 269 DVLOG(2) << "Sending watch time update."; | 322 if (!log_event->params.empty()) |
| 270 media_log_->AddEvent(std::move(log_event)); | 323 media_log_->AddEvent(std::move(log_event)); |
| 271 } | |
| 272 | 324 |
| 273 if (is_power_change_pending) { | 325 if (is_power_change_pending) { |
| 274 // Invert battery power status here instead of using the value returned by | 326 // Invert battery power status here instead of using the value returned by |
| 275 // the PowerObserver since there may be a pending OnPowerStateChange(). | 327 // the PowerObserver since there may be a pending OnPowerStateChange(). |
| 276 is_on_battery_power_ = !is_on_battery_power_; | 328 is_on_battery_power_ = !is_on_battery_power_; |
| 277 | 329 |
| 278 start_timestamp_for_power_ = end_timestamp_for_power_; | 330 start_timestamp_for_power_ = end_timestamp_for_power_; |
| 279 end_timestamp_for_power_ = kNoTimestamp; | 331 end_timestamp_for_power_ = kNoTimestamp; |
| 280 } | 332 } |
| 281 | 333 |
| 282 // Stop the timer if this is supposed to be our last tick. | 334 // Stop the timer if this is supposed to be our last tick. |
| 283 if (is_finalizing) { | 335 if (is_finalizing) { |
| 284 end_timestamp_ = kNoTimestamp; | 336 end_timestamp_ = kNoTimestamp; |
| 285 reporting_timer_.Stop(); | 337 reporting_timer_.Stop(); |
| 286 } | 338 } |
| 287 } | 339 } |
| 288 | 340 |
| 289 } // namespace media | 341 } // namespace media |
| OLD | NEW |