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

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: Add more tests. 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/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);
sandersd (OOO until July 31) 2016/08/03 22:45:58 It looks like this is correct in the background pl
DaleCurtis 2016/08/04 19:14:59 This signal is a proxy for "not visible," so even
sandersd (OOO until July 31) 2016/08/04 20:01:00 Do you still want to start the reporting timer in
DaleCurtis 2016/08/04 20:10:13 StartReportingTimer() will bail if start condition
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);
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698