1// Copyright (c) 2012 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/media_internals.h"
6
7#include "base/strings/string16.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/stringprintf.h"
10#include "content/public/browser/browser_thread.h"
11#include "content/public/browser/web_ui.h"
12#include "media/audio/audio_parameters.h"
13#include "media/base/media_log.h"
14#include "media/base/media_log_event.h"
15
16namespace {
17
18static base::LazyInstance<content::MediaInternals>::Leaky g_media_internals =
19    LAZY_INSTANCE_INITIALIZER;
20
21base::string16 SerializeUpdate(const std::string& function,
22                               const base::Value* value) {
23  return content::WebUI::GetJavascriptCall(
24      function, std::vector<const base::Value*>(1, value));
25}
26
27std::string EffectsToString(int effects) {
28  if (effects == media::AudioParameters::NO_EFFECTS)
29    return "NO_EFFECTS";
30
31  struct {
32    int flag;
33    const char* name;
34  } flags[] = {
35    { media::AudioParameters::ECHO_CANCELLER, "ECHO_CANCELLER" },
36    { media::AudioParameters::DUCKING, "DUCKING" },
37    { media::AudioParameters::KEYBOARD_MIC, "KEYBOARD_MIC" },
38  };
39
40  std::string ret;
41  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(flags); ++i) {
42    if (effects & flags[i].flag) {
43      if (!ret.empty())
44        ret += " | ";
45      ret += flags[i].name;
46      effects &= ~flags[i].flag;
47    }
48  }
49
50  if (effects) {
51    if (!ret.empty())
52      ret += " | ";
53    ret += base::IntToString(effects);
54  }
55
56  return ret;
57}
58
59const char kAudioLogStatusKey[] = "status";
60const char kAudioLogUpdateFunction[] = "media.updateAudioComponent";
61
62}  // namespace
63
64namespace content {
65
66class AudioLogImpl : public media::AudioLog {
67 public:
68  AudioLogImpl(int owner_id,
69               media::AudioLogFactory::AudioComponent component,
70               content::MediaInternals* media_internals);
71  virtual ~AudioLogImpl();
72
73  virtual void OnCreated(int component_id,
74                         const media::AudioParameters& params,
75                         const std::string& device_id) OVERRIDE;
76  virtual void OnStarted(int component_id) OVERRIDE;
77  virtual void OnStopped(int component_id) OVERRIDE;
78  virtual void OnClosed(int component_id) OVERRIDE;
79  virtual void OnError(int component_id) OVERRIDE;
80  virtual void OnSetVolume(int component_id, double volume) OVERRIDE;
81
82 private:
83  void SendSingleStringUpdate(int component_id,
84                              const std::string& key,
85                              const std::string& value);
86  void StoreComponentMetadata(int component_id, base::DictionaryValue* dict);
87  std::string FormatCacheKey(int component_id);
88
89  const int owner_id_;
90  const media::AudioLogFactory::AudioComponent component_;
91  content::MediaInternals* const media_internals_;
92
93  DISALLOW_COPY_AND_ASSIGN(AudioLogImpl);
94};
95
96AudioLogImpl::AudioLogImpl(int owner_id,
97                           media::AudioLogFactory::AudioComponent component,
98                           content::MediaInternals* media_internals)
99    : owner_id_(owner_id),
100      component_(component),
101      media_internals_(media_internals) {}
102
103AudioLogImpl::~AudioLogImpl() {}
104
105void AudioLogImpl::OnCreated(int component_id,
106                             const media::AudioParameters& params,
107                             const std::string& device_id) {
108  base::DictionaryValue dict;
109  StoreComponentMetadata(component_id, &dict);
110
111  dict.SetString(kAudioLogStatusKey, "created");
112  dict.SetString("device_id", device_id);
113  dict.SetInteger("frames_per_buffer", params.frames_per_buffer());
114  dict.SetInteger("sample_rate", params.sample_rate());
115  dict.SetInteger("channels", params.channels());
116  dict.SetString("channel_layout",
117                 ChannelLayoutToString(params.channel_layout()));
118  dict.SetString("effects", EffectsToString(params.effects()));
119
120  media_internals_->SendUpdateAndCache(
121      FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
122}
123
124void AudioLogImpl::OnStarted(int component_id) {
125  SendSingleStringUpdate(component_id, kAudioLogStatusKey, "started");
126}
127
128void AudioLogImpl::OnStopped(int component_id) {
129  SendSingleStringUpdate(component_id, kAudioLogStatusKey, "stopped");
130}
131
132void AudioLogImpl::OnClosed(int component_id) {
133  base::DictionaryValue dict;
134  StoreComponentMetadata(component_id, &dict);
135  dict.SetString(kAudioLogStatusKey, "closed");
136  media_internals_->SendUpdateAndPurgeCache(
137      FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
138}
139
140void AudioLogImpl::OnError(int component_id) {
141  SendSingleStringUpdate(component_id, "error_occurred", "true");
142}
143
144void AudioLogImpl::OnSetVolume(int component_id, double volume) {
145  base::DictionaryValue dict;
146  StoreComponentMetadata(component_id, &dict);
147  dict.SetDouble("volume", volume);
148  media_internals_->SendUpdateAndCache(
149      FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
150}
151
152std::string AudioLogImpl::FormatCacheKey(int component_id) {
153  return base::StringPrintf("%d:%d:%d", owner_id_, component_, component_id);
154}
155
156void AudioLogImpl::SendSingleStringUpdate(int component_id,
157                                          const std::string& key,
158                                          const std::string& value) {
159  base::DictionaryValue dict;
160  StoreComponentMetadata(component_id, &dict);
161  dict.SetString(key, value);
162  media_internals_->SendUpdateAndCache(
163      FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
164}
165
166void AudioLogImpl::StoreComponentMetadata(int component_id,
167                                          base::DictionaryValue* dict) {
168  dict->SetInteger("owner_id", owner_id_);
169  dict->SetInteger("component_id", component_id);
170  dict->SetInteger("component_type", component_);
171}
172
173MediaInternals* MediaInternals::GetInstance() {
174  return g_media_internals.Pointer();
175}
176
177MediaInternals::MediaInternals() : owner_ids_() {}
178MediaInternals::~MediaInternals() {}
179
180void MediaInternals::OnMediaEvents(
181    int render_process_id, const std::vector<media::MediaLogEvent>& events) {
182  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
183  // Notify observers that |event| has occurred.
184  for (std::vector<media::MediaLogEvent>::const_iterator event = events.begin();
185       event != events.end(); ++event) {
186    base::DictionaryValue dict;
187    dict.SetInteger("renderer", render_process_id);
188    dict.SetInteger("player", event->id);
189    dict.SetString("type", media::MediaLog::EventTypeToString(event->type));
190
191    // TODO(dalecurtis): This is technically not correct.  TimeTicks "can't" be
192    // converted to to a human readable time format.  See base/time/time.h.
193    const double ticks = event->time.ToInternalValue();
194    const double ticks_millis = ticks / base::Time::kMicrosecondsPerMillisecond;
195    dict.SetDouble("ticksMillis", ticks_millis);
196    dict.Set("params", event->params.DeepCopy());
197    SendUpdate(SerializeUpdate("media.onMediaEvent", &dict));
198  }
199}
200
201void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) {
202  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
203  update_callbacks_.push_back(callback);
204}
205
206void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
207  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
208  for (size_t i = 0; i < update_callbacks_.size(); ++i) {
209    if (update_callbacks_[i].Equals(callback)) {
210      update_callbacks_.erase(update_callbacks_.begin() + i);
211      return;
212    }
213  }
214  NOTREACHED();
215}
216
217void MediaInternals::SendEverything() {
218  base::string16 everything_update;
219  {
220    base::AutoLock auto_lock(lock_);
221    everything_update = SerializeUpdate(
222        "media.onReceiveEverything", &cached_data_);
223  }
224  SendUpdate(everything_update);
225}
226
227void MediaInternals::SendUpdate(const base::string16& update) {
228  // SendUpdate() may be called from any thread, but must run on the IO thread.
229  // TODO(dalecurtis): This is pretty silly since the update callbacks simply
230  // forward the calls to the UI thread.  We should avoid the extra hop.
231  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
232    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
233        &MediaInternals::SendUpdate, base::Unretained(this), update));
234    return;
235  }
236
237  for (size_t i = 0; i < update_callbacks_.size(); i++)
238    update_callbacks_[i].Run(update);
239}
240
241scoped_ptr<media::AudioLog> MediaInternals::CreateAudioLog(
242    AudioComponent component) {
243  base::AutoLock auto_lock(lock_);
244  return scoped_ptr<media::AudioLog>(new AudioLogImpl(
245      owner_ids_[component]++, component, this));
246}
247
248void MediaInternals::SendUpdateAndCache(const std::string& cache_key,
249                                        const std::string& function,
250                                        const base::DictionaryValue* value) {
251  SendUpdate(SerializeUpdate(function, value));
252
253  base::AutoLock auto_lock(lock_);
254  if (!cached_data_.HasKey(cache_key)) {
255    cached_data_.Set(cache_key, value->DeepCopy());
256    return;
257  }
258
259  base::DictionaryValue* existing_dict = NULL;
260  CHECK(cached_data_.GetDictionary(cache_key, &existing_dict));
261  existing_dict->MergeDictionary(value);
262}
263
264void MediaInternals::SendUpdateAndPurgeCache(
265    const std::string& cache_key,
266    const std::string& function,
267    const base::DictionaryValue* value) {
268  SendUpdate(SerializeUpdate(function, value));
269
270  base::AutoLock auto_lock(lock_);
271  scoped_ptr<base::Value> out_value;
272  CHECK(cached_data_.Remove(cache_key, &out_value));
273}
274
275}  // namespace content
276