1// Copyright 2014 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 "content/browser/media/audio_stream_monitor.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "content/browser/web_contents/web_contents_impl.h"
10#include "content/public/browser/browser_thread.h"
11#include "content/public/browser/invalidate_type.h"
12#include "content/public/browser/render_frame_host.h"
13
14namespace content {
15
16namespace {
17
18AudioStreamMonitor* AudioStreamMonitorFromRenderFrame(int render_process_id,
19                                                      int render_frame_id) {
20  DCHECK_CURRENTLY_ON(BrowserThread::UI);
21  WebContentsImpl* const web_contents =
22      static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(
23          RenderFrameHost::FromID(render_process_id, render_frame_id)));
24  return web_contents ? web_contents->audio_stream_monitor() : NULL;
25}
26
27}  // namespace
28
29AudioStreamMonitor::AudioStreamMonitor(WebContents* contents)
30    : web_contents_(contents),
31      clock_(&default_tick_clock_),
32      was_recently_audible_(false) {
33  DCHECK(web_contents_);
34}
35
36AudioStreamMonitor::~AudioStreamMonitor() {}
37
38bool AudioStreamMonitor::WasRecentlyAudible() const {
39  DCHECK(thread_checker_.CalledOnValidThread());
40  return was_recently_audible_;
41}
42
43// static
44void AudioStreamMonitor::StartMonitoringStream(
45    int render_process_id,
46    int render_frame_id,
47    int stream_id,
48    const ReadPowerAndClipCallback& read_power_callback) {
49  if (!monitoring_available())
50    return;
51  BrowserThread::PostTask(BrowserThread::UI,
52                          FROM_HERE,
53                          base::Bind(&StartMonitoringHelper,
54                                     render_process_id,
55                                     render_frame_id,
56                                     stream_id,
57                                     read_power_callback));
58}
59
60// static
61void AudioStreamMonitor::StopMonitoringStream(int render_process_id,
62                                              int render_frame_id,
63                                              int stream_id) {
64  if (!monitoring_available())
65    return;
66  BrowserThread::PostTask(BrowserThread::UI,
67                          FROM_HERE,
68                          base::Bind(&StopMonitoringHelper,
69                                     render_process_id,
70                                     render_frame_id,
71                                     stream_id));
72}
73
74// static
75void AudioStreamMonitor::StartMonitoringHelper(
76    int render_process_id,
77    int render_frame_id,
78    int stream_id,
79    const ReadPowerAndClipCallback& read_power_callback) {
80  DCHECK_CURRENTLY_ON(BrowserThread::UI);
81  AudioStreamMonitor* const monitor =
82      AudioStreamMonitorFromRenderFrame(render_process_id, render_frame_id);
83  if (monitor) {
84    monitor->StartMonitoringStreamOnUIThread(
85        render_process_id, stream_id, read_power_callback);
86  }
87}
88
89// static
90void AudioStreamMonitor::StopMonitoringHelper(int render_process_id,
91                                              int render_frame_id,
92                                              int stream_id) {
93  DCHECK_CURRENTLY_ON(BrowserThread::UI);
94  AudioStreamMonitor* const monitor =
95      AudioStreamMonitorFromRenderFrame(render_process_id, render_frame_id);
96  if (monitor)
97    monitor->StopMonitoringStreamOnUIThread(render_process_id, stream_id);
98}
99
100void AudioStreamMonitor::StartMonitoringStreamOnUIThread(
101    int render_process_id,
102    int stream_id,
103    const ReadPowerAndClipCallback& read_power_callback) {
104  DCHECK(thread_checker_.CalledOnValidThread());
105  DCHECK(!read_power_callback.is_null());
106  poll_callbacks_[StreamID(render_process_id, stream_id)] = read_power_callback;
107  if (!poll_timer_.IsRunning()) {
108    poll_timer_.Start(
109        FROM_HERE,
110        base::TimeDelta::FromSeconds(1) / kPowerMeasurementsPerSecond,
111        base::Bind(&AudioStreamMonitor::Poll, base::Unretained(this)));
112  }
113}
114
115void AudioStreamMonitor::StopMonitoringStreamOnUIThread(int render_process_id,
116                                                        int stream_id) {
117  DCHECK(thread_checker_.CalledOnValidThread());
118  poll_callbacks_.erase(StreamID(render_process_id, stream_id));
119  if (poll_callbacks_.empty())
120    poll_timer_.Stop();
121}
122
123void AudioStreamMonitor::Poll() {
124  for (StreamPollCallbackMap::const_iterator it = poll_callbacks_.begin();
125       it != poll_callbacks_.end();
126       ++it) {
127    // TODO(miu): A new UI for delivering specific power level and clipping
128    // information is still in the works.  For now, we throw away all
129    // information except for "is it audible?"
130    const float power_dbfs = it->second.Run().first;
131    const float kSilenceThresholdDBFS = -72.24719896f;
132    if (power_dbfs >= kSilenceThresholdDBFS) {
133      last_blurt_time_ = clock_->NowTicks();
134      MaybeToggle();
135      break;  // No need to poll remaining streams.
136    }
137  }
138}
139
140void AudioStreamMonitor::MaybeToggle() {
141  const bool indicator_was_on = was_recently_audible_;
142  const base::TimeTicks off_time =
143      last_blurt_time_ + base::TimeDelta::FromMilliseconds(kHoldOnMilliseconds);
144  const base::TimeTicks now = clock_->NowTicks();
145  const bool should_indicator_be_on = now < off_time;
146
147  if (should_indicator_be_on != indicator_was_on) {
148    was_recently_audible_ = should_indicator_be_on;
149    web_contents_->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
150  }
151
152  if (!should_indicator_be_on) {
153    off_timer_.Stop();
154  } else if (!off_timer_.IsRunning()) {
155    off_timer_.Start(
156        FROM_HERE,
157        off_time - now,
158        base::Bind(&AudioStreamMonitor::MaybeToggle, base::Unretained(this)));
159  }
160}
161
162}  // namespace content
163