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); | |
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 | |
OLD | NEW |