Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1029)

Side by Side Diff: media/blink/watch_time_reporter.cc

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

Powered by Google App Engine
This is Rietveld 408576698