blob: b16b66be60af3b9f9c5614eb911e7e52775d1940 [file] [log] [blame]
[email protected]11158e2d2013-02-01 02:31:561// 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
prabhur53bb9182014-11-13 03:25:177#include "base/metrics/histogram.h"
[email protected]348fbaac2013-06-11 06:31:518#include "base/strings/string16.h"
[email protected]9367e032014-03-07 19:42:379#include "base/strings/string_number_conversions.h"
[email protected]348fbaac2013-06-11 06:31:5110#include "base/strings/stringprintf.h"
[email protected]11158e2d2013-02-01 02:31:5611#include "content/public/browser/browser_thread.h"
prabhur53bb9182014-11-13 03:25:1712#include "content/public/browser/notification_observer.h"
13#include "content/public/browser/notification_registrar.h"
14#include "content/public/browser/notification_service.h"
15#include "content/public/browser/notification_types.h"
16#include "content/public/browser/render_process_host.h"
[email protected]11158e2d2013-02-01 02:31:5617#include "content/public/browser/web_ui.h"
[email protected]e2fd1f72013-08-16 00:34:2518#include "media/audio/audio_parameters.h"
[email protected]11158e2d2013-02-01 02:31:5619#include "media/base/media_log_event.h"
prabhur957d46c72014-11-19 03:01:1620#include "media/filters/gpu_video_decoder.h"
[email protected]11158e2d2013-02-01 02:31:5621
[email protected]69946cf2013-11-27 00:11:4222namespace {
23
24static base::LazyInstance<content::MediaInternals>::Leaky g_media_internals =
25 LAZY_INSTANCE_INITIALIZER;
26
[email protected]fcf75d42013-12-03 20:11:2627base::string16 SerializeUpdate(const std::string& function,
28 const base::Value* value) {
[email protected]69946cf2013-11-27 00:11:4229 return content::WebUI::GetJavascriptCall(
30 function, std::vector<const base::Value*>(1, value));
31}
32
[email protected]9367e032014-03-07 19:42:3733std::string EffectsToString(int effects) {
34 if (effects == media::AudioParameters::NO_EFFECTS)
35 return "NO_EFFECTS";
36
37 struct {
38 int flag;
39 const char* name;
40 } flags[] = {
41 { media::AudioParameters::ECHO_CANCELLER, "ECHO_CANCELLER" },
42 { media::AudioParameters::DUCKING, "DUCKING" },
[email protected]81495eb2014-03-19 06:08:1843 { media::AudioParameters::KEYBOARD_MIC, "KEYBOARD_MIC" },
[email protected]9367e032014-03-07 19:42:3744 };
45
46 std::string ret;
viettrungluu2dfaba72014-10-16 05:30:2547 for (size_t i = 0; i < arraysize(flags); ++i) {
[email protected]9367e032014-03-07 19:42:3748 if (effects & flags[i].flag) {
49 if (!ret.empty())
50 ret += " | ";
51 ret += flags[i].name;
52 effects &= ~flags[i].flag;
53 }
54 }
55
56 if (effects) {
57 if (!ret.empty())
58 ret += " | ";
59 ret += base::IntToString(effects);
60 }
61
62 return ret;
63}
64
[email protected]69946cf2013-11-27 00:11:4265const char kAudioLogStatusKey[] = "status";
66const char kAudioLogUpdateFunction[] = "media.updateAudioComponent";
67
68} // namespace
69
[email protected]11158e2d2013-02-01 02:31:5670namespace content {
71
[email protected]69946cf2013-11-27 00:11:4272class AudioLogImpl : public media::AudioLog {
73 public:
74 AudioLogImpl(int owner_id,
75 media::AudioLogFactory::AudioComponent component,
76 content::MediaInternals* media_internals);
dchengc2282aa2014-10-21 12:07:5877 ~AudioLogImpl() override;
[email protected]69946cf2013-11-27 00:11:4278
dchengc2282aa2014-10-21 12:07:5879 void OnCreated(int component_id,
80 const media::AudioParameters& params,
81 const std::string& device_id) override;
82 void OnStarted(int component_id) override;
83 void OnStopped(int component_id) override;
84 void OnClosed(int component_id) override;
85 void OnError(int component_id) override;
86 void OnSetVolume(int component_id, double volume) override;
[email protected]69946cf2013-11-27 00:11:4287
88 private:
89 void SendSingleStringUpdate(int component_id,
90 const std::string& key,
91 const std::string& value);
92 void StoreComponentMetadata(int component_id, base::DictionaryValue* dict);
93 std::string FormatCacheKey(int component_id);
94
95 const int owner_id_;
96 const media::AudioLogFactory::AudioComponent component_;
97 content::MediaInternals* const media_internals_;
98
99 DISALLOW_COPY_AND_ASSIGN(AudioLogImpl);
100};
101
102AudioLogImpl::AudioLogImpl(int owner_id,
103 media::AudioLogFactory::AudioComponent component,
104 content::MediaInternals* media_internals)
105 : owner_id_(owner_id),
106 component_(component),
107 media_internals_(media_internals) {}
108
109AudioLogImpl::~AudioLogImpl() {}
110
111void AudioLogImpl::OnCreated(int component_id,
112 const media::AudioParameters& params,
[email protected]25d7f892014-02-13 15:22:45113 const std::string& device_id) {
[email protected]69946cf2013-11-27 00:11:42114 base::DictionaryValue dict;
115 StoreComponentMetadata(component_id, &dict);
116
117 dict.SetString(kAudioLogStatusKey, "created");
[email protected]25d7f892014-02-13 15:22:45118 dict.SetString("device_id", device_id);
[email protected]69946cf2013-11-27 00:11:42119 dict.SetInteger("frames_per_buffer", params.frames_per_buffer());
120 dict.SetInteger("sample_rate", params.sample_rate());
[email protected]ad82f412013-11-27 04:20:41121 dict.SetInteger("channels", params.channels());
122 dict.SetString("channel_layout",
[email protected]69946cf2013-11-27 00:11:42123 ChannelLayoutToString(params.channel_layout()));
[email protected]9367e032014-03-07 19:42:37124 dict.SetString("effects", EffectsToString(params.effects()));
[email protected]69946cf2013-11-27 00:11:42125
mcasasfcb5c7de2014-10-11 20:22:17126 media_internals_->SendUpdateAndCacheAudioStreamKey(
[email protected]69946cf2013-11-27 00:11:42127 FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
128}
129
130void AudioLogImpl::OnStarted(int component_id) {
131 SendSingleStringUpdate(component_id, kAudioLogStatusKey, "started");
132}
133
134void AudioLogImpl::OnStopped(int component_id) {
135 SendSingleStringUpdate(component_id, kAudioLogStatusKey, "stopped");
136}
137
138void AudioLogImpl::OnClosed(int component_id) {
139 base::DictionaryValue dict;
140 StoreComponentMetadata(component_id, &dict);
141 dict.SetString(kAudioLogStatusKey, "closed");
mcasasfcb5c7de2014-10-11 20:22:17142 media_internals_->SendUpdateAndPurgeAudioStreamCache(
[email protected]69946cf2013-11-27 00:11:42143 FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
144}
145
146void AudioLogImpl::OnError(int component_id) {
147 SendSingleStringUpdate(component_id, "error_occurred", "true");
148}
149
150void AudioLogImpl::OnSetVolume(int component_id, double volume) {
151 base::DictionaryValue dict;
152 StoreComponentMetadata(component_id, &dict);
153 dict.SetDouble("volume", volume);
mcasasfcb5c7de2014-10-11 20:22:17154 media_internals_->SendUpdateAndCacheAudioStreamKey(
[email protected]69946cf2013-11-27 00:11:42155 FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
156}
157
158std::string AudioLogImpl::FormatCacheKey(int component_id) {
159 return base::StringPrintf("%d:%d:%d", owner_id_, component_, component_id);
160}
161
162void AudioLogImpl::SendSingleStringUpdate(int component_id,
163 const std::string& key,
164 const std::string& value) {
165 base::DictionaryValue dict;
166 StoreComponentMetadata(component_id, &dict);
167 dict.SetString(key, value);
mcasasfcb5c7de2014-10-11 20:22:17168 media_internals_->SendUpdateAndCacheAudioStreamKey(
[email protected]69946cf2013-11-27 00:11:42169 FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
170}
171
172void AudioLogImpl::StoreComponentMetadata(int component_id,
173 base::DictionaryValue* dict) {
174 dict->SetInteger("owner_id", owner_id_);
175 dict->SetInteger("component_id", component_id);
176 dict->SetInteger("component_type", component_);
177}
178
prabhur53bb9182014-11-13 03:25:17179class MediaInternals::MediaInternalsUMAHandler : public NotificationObserver {
180 public:
181 MediaInternalsUMAHandler();
182
183 // NotificationObserver implementation.
184 void Observe(int type,
185 const NotificationSource& source,
186 const NotificationDetails& details) override;
187
188 // Reports the pipeline status to UMA for every player
189 // associated with the renderer process and then deletes the player state.
190 void LogAndClearPlayersInRenderer(int render_process_id);
191
192 // Helper function to save the event payload to RendererPlayerMap.
193 void SavePlayerState(const media::MediaLogEvent& event,
194 int render_process_id);
195
196 private:
197 struct PipelineInfo {
198 media::PipelineStatus last_pipeline_status;
199 bool has_audio;
200 bool has_video;
201 std::string audio_codec_name;
202 std::string video_codec_name;
203 std::string video_decoder;
204 PipelineInfo()
205 : last_pipeline_status(media::PIPELINE_OK),
206 has_audio(false),
207 has_video(false) {}
208 };
209
210 // Helper function to report PipelineStatus associated with a player to UMA.
211 void ReportUMAForPipelineStatus(const PipelineInfo& player_info);
212
213 // Key is playerid
214 typedef std::map<int, PipelineInfo> PlayerInfoMap;
215
216 // Key is renderer id
217 typedef std::map<int, PlayerInfoMap> RendererPlayerMap;
218
219 // Stores player information per renderer
220 RendererPlayerMap renderer_info_;
221
222 NotificationRegistrar registrar_;
223
224 DISALLOW_COPY_AND_ASSIGN(MediaInternalsUMAHandler);
225};
226
227MediaInternals::MediaInternalsUMAHandler::MediaInternalsUMAHandler() {
228 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
229 NotificationService::AllBrowserContextsAndSources());
230}
231
232void MediaInternals::MediaInternalsUMAHandler::Observe(
233 int type,
234 const NotificationSource& source,
235 const NotificationDetails& details) {
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
237 DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
238 RenderProcessHost* process = Source<RenderProcessHost>(source).ptr();
239
240 // Post the task to the IO thread to avoid race in updating renderer_info_ map
241 // by both SavePlayerState & LogAndClearPlayersInRenderer from different
242 // threads.
243 // Using base::Unretained() on MediaInternalsUMAHandler is safe since
244 // it is owned by MediaInternals and share the same lifetime
245 BrowserThread::PostTask(
246 BrowserThread::IO, FROM_HERE,
247 base::Bind(&MediaInternalsUMAHandler::LogAndClearPlayersInRenderer,
248 base::Unretained(this), process->GetID()));
249}
250
251void MediaInternals::MediaInternalsUMAHandler::SavePlayerState(
252 const media::MediaLogEvent& event,
253 int render_process_id) {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
255 PlayerInfoMap& player_info = renderer_info_[render_process_id];
256 switch (event.type) {
257 case media::MediaLogEvent::WEBMEDIAPLAYER_CREATED: {
258 // Nothing to do here
259 break;
260 }
261 case media::MediaLogEvent::PIPELINE_ERROR: {
262 int status;
263 event.params.GetInteger("pipeline_error", &status);
264 player_info[event.id].last_pipeline_status =
265 static_cast<media::PipelineStatus>(status);
266 break;
267 }
268 case media::MediaLogEvent::PROPERTY_CHANGE:
269 if (event.params.HasKey("found_audio_stream")) {
270 event.params.GetBoolean("found_audio_stream",
271 &player_info[event.id].has_audio);
272 }
273 if (event.params.HasKey("found_video_stream")) {
274 event.params.GetBoolean("found_video_stream",
275 &player_info[event.id].has_video);
276 }
277 if (event.params.HasKey("audio_codec_name")) {
278 event.params.GetString("audio_codec_name",
279 &player_info[event.id].audio_codec_name);
280 }
281 if (event.params.HasKey("video_codec_name")) {
282 event.params.GetString("video_codec_name",
283 &player_info[event.id].video_codec_name);
284 }
285 if (event.params.HasKey("video_decoder")) {
286 event.params.GetString("video_decoder",
287 &player_info[event.id].video_decoder);
288 }
289 break;
290 default:
291 break;
292 }
293 return;
294}
295
296void MediaInternals::MediaInternalsUMAHandler::ReportUMAForPipelineStatus(
297 const PipelineInfo& player_info) {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
299 if (player_info.has_video && player_info.has_audio) {
300 if (player_info.video_codec_name == "vp8") {
301 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioVideo.VP8",
302 player_info.last_pipeline_status,
303 media::PIPELINE_STATUS_MAX + 1);
304 } else if (player_info.video_codec_name == "vp9") {
305 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioVideo.VP9",
306 player_info.last_pipeline_status,
307 media::PIPELINE_STATUS_MAX + 1);
308 } else if (player_info.video_codec_name == "h264") {
prabhur957d46c72014-11-19 03:01:16309 if (player_info.video_decoder == media::GpuVideoDecoder::kDecoderName) {
prabhur53bb9182014-11-13 03:25:17310 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioVideo.HW.H264",
311 player_info.last_pipeline_status,
312 media::PIPELINE_STATUS_MAX + 1);
313 } else {
314 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioVideo.SW.H264",
315 player_info.last_pipeline_status,
316 media::PIPELINE_STATUS_MAX + 1);
317 }
318 } else {
319 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioVideo",
320 player_info.last_pipeline_status,
321 media::PIPELINE_STATUS_MAX + 1);
322 }
323 } else if (player_info.has_audio) {
324 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.Audio",
325 player_info.last_pipeline_status,
326 media::PIPELINE_STATUS_MAX + 1);
327 } else if (player_info.has_video) {
328 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.Video",
329 player_info.last_pipeline_status,
330 media::PIPELINE_STATUS_MAX + 1);
331 } else {
332 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.Unsupported",
333 player_info.last_pipeline_status,
334 media::PIPELINE_STATUS_MAX + 1);
335 }
336}
337
338void MediaInternals::MediaInternalsUMAHandler::LogAndClearPlayersInRenderer(
339 int render_process_id) {
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
341 auto players_it = renderer_info_.find(render_process_id);
342 if (players_it == renderer_info_.end())
343 return;
344 auto it = players_it->second.begin();
345 while (it != players_it->second.end()) {
346 ReportUMAForPipelineStatus(it->second);
347 players_it->second.erase(it++);
348 }
349}
350
[email protected]11158e2d2013-02-01 02:31:56351MediaInternals* MediaInternals::GetInstance() {
[email protected]69946cf2013-11-27 00:11:42352 return g_media_internals.Pointer();
[email protected]11158e2d2013-02-01 02:31:56353}
354
prabhur53bb9182014-11-13 03:25:17355MediaInternals::MediaInternals()
356 : owner_ids_(), uma_handler_(new MediaInternalsUMAHandler()) {
357}
358
[email protected]11158e2d2013-02-01 02:31:56359MediaInternals::~MediaInternals() {}
360
[email protected]0e7ee582013-05-04 14:06:59361void MediaInternals::OnMediaEvents(
362 int render_process_id, const std::vector<media::MediaLogEvent>& events) {
[email protected]11158e2d2013-02-01 02:31:56363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]69946cf2013-11-27 00:11:42364 // Notify observers that |event| has occurred.
prabhur53bb9182014-11-13 03:25:17365 for (auto event = events.begin(); event != events.end(); ++event) {
[email protected]0e7ee582013-05-04 14:06:59366 base::DictionaryValue dict;
367 dict.SetInteger("renderer", render_process_id);
368 dict.SetInteger("player", event->id);
369 dict.SetString("type", media::MediaLog::EventTypeToString(event->type));
[email protected]fbc46bd2013-06-26 04:21:41370
[email protected]69946cf2013-11-27 00:11:42371 // TODO(dalecurtis): This is technically not correct. TimeTicks "can't" be
372 // converted to to a human readable time format. See base/time/time.h.
373 const double ticks = event->time.ToInternalValue();
374 const double ticks_millis = ticks / base::Time::kMicrosecondsPerMillisecond;
[email protected]fbc46bd2013-06-26 04:21:41375 dict.SetDouble("ticksMillis", ticks_millis);
prabhur53bb9182014-11-13 03:25:17376
377 // Convert PipelineStatus to human readable string
378 if (event->type == media::MediaLogEvent::PIPELINE_ERROR) {
379 int status;
380 event->params.GetInteger("pipeline_error", &status);
381 media::PipelineStatus error = static_cast<media::PipelineStatus>(status);
382 dict.SetString("params.pipeline_error",
383 media::MediaLog::PipelineStatusToString(error));
384 } else {
385 dict.Set("params", event->params.DeepCopy());
386 }
387
[email protected]69946cf2013-11-27 00:11:42388 SendUpdate(SerializeUpdate("media.onMediaEvent", &dict));
prabhur53bb9182014-11-13 03:25:17389 uma_handler_->SavePlayerState(*event, render_process_id);
[email protected]0e7ee582013-05-04 14:06:59390 }
[email protected]11158e2d2013-02-01 02:31:56391}
392
393void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) {
[email protected]69946cf2013-11-27 00:11:42394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]11158e2d2013-02-01 02:31:56395 update_callbacks_.push_back(callback);
396}
397
398void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
[email protected]69946cf2013-11-27 00:11:42399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]11158e2d2013-02-01 02:31:56400 for (size_t i = 0; i < update_callbacks_.size(); ++i) {
401 if (update_callbacks_[i].Equals(callback)) {
402 update_callbacks_.erase(update_callbacks_.begin() + i);
403 return;
404 }
405 }
406 NOTREACHED();
407}
408
mcasasfcb5c7de2014-10-11 20:22:17409void MediaInternals::SendAudioStreamData() {
410 base::string16 audio_stream_update;
[email protected]69946cf2013-11-27 00:11:42411 {
412 base::AutoLock auto_lock(lock_);
mcasasfcb5c7de2014-10-11 20:22:17413 audio_stream_update = SerializeUpdate(
414 "media.onReceiveAudioStreamData", &audio_streams_cached_data_);
[email protected]11158e2d2013-02-01 02:31:56415 }
mcasasfcb5c7de2014-10-11 20:22:17416 SendUpdate(audio_stream_update);
417}
418
burnik71963562014-10-17 14:57:14419void MediaInternals::SendVideoCaptureDeviceCapabilities() {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
421 SendUpdate(SerializeUpdate("media.onReceiveVideoCaptureCapabilities",
422 &video_capture_capabilities_cached_data_));
423}
424
mcasasfcb5c7de2014-10-11 20:22:17425void MediaInternals::UpdateVideoCaptureDeviceCapabilities(
426 const media::VideoCaptureDeviceInfos& video_capture_device_infos) {
burnik71963562014-10-17 14:57:14427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
428 video_capture_capabilities_cached_data_.Clear();
mcasasfcb5c7de2014-10-11 20:22:17429
430 for (const auto& video_capture_device_info : video_capture_device_infos) {
burnik71963562014-10-17 14:57:14431 base::ListValue* format_list = new base::ListValue();
432 for (const auto& format : video_capture_device_info.supported_formats)
433 format_list->AppendString(format.ToString());
434
435 base::DictionaryValue* device_dict = new base::DictionaryValue();
436 device_dict->SetString("id", video_capture_device_info.name.id());
437 device_dict->SetString(
438 "name", video_capture_device_info.name.GetNameAndModel());
439 device_dict->Set("formats", format_list);
mcasasfcb5c7de2014-10-11 20:22:17440#if defined(OS_WIN) || defined(OS_MACOSX)
burnik4bf4ed12014-10-21 22:35:01441 device_dict->SetString(
burnik71963562014-10-17 14:57:14442 "captureApi",
burnik4bf4ed12014-10-21 22:35:01443 video_capture_device_info.name.GetCaptureApiTypeString());
mcasasfcb5c7de2014-10-11 20:22:17444#endif
burnik71963562014-10-17 14:57:14445 video_capture_capabilities_cached_data_.Append(device_dict);
mcasasfcb5c7de2014-10-11 20:22:17446 }
burnik71963562014-10-17 14:57:14447
448 if (update_callbacks_.size() > 0)
449 SendVideoCaptureDeviceCapabilities();
mcasasfcb5c7de2014-10-11 20:22:17450}
451
452scoped_ptr<media::AudioLog> MediaInternals::CreateAudioLog(
453 AudioComponent component) {
454 base::AutoLock auto_lock(lock_);
455 return scoped_ptr<media::AudioLog>(new AudioLogImpl(
456 owner_ids_[component]++, component, this));
[email protected]e2fd1f72013-08-16 00:34:25457}
458
[email protected]fcf75d42013-12-03 20:11:26459void MediaInternals::SendUpdate(const base::string16& update) {
[email protected]69946cf2013-11-27 00:11:42460 // SendUpdate() may be called from any thread, but must run on the IO thread.
461 // TODO(dalecurtis): This is pretty silly since the update callbacks simply
462 // forward the calls to the UI thread. We should avoid the extra hop.
463 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
464 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
465 &MediaInternals::SendUpdate, base::Unretained(this), update));
[email protected]11158e2d2013-02-01 02:31:56466 return;
[email protected]69946cf2013-11-27 00:11:42467 }
[email protected]11158e2d2013-02-01 02:31:56468
[email protected]11158e2d2013-02-01 02:31:56469 for (size_t i = 0; i < update_callbacks_.size(); i++)
470 update_callbacks_[i].Run(update);
471}
472
mcasasfcb5c7de2014-10-11 20:22:17473void MediaInternals::SendUpdateAndCacheAudioStreamKey(
474 const std::string& cache_key,
475 const std::string& function,
476 const base::DictionaryValue* value) {
[email protected]69946cf2013-11-27 00:11:42477 SendUpdate(SerializeUpdate(function, value));
478
479 base::AutoLock auto_lock(lock_);
mcasasfcb5c7de2014-10-11 20:22:17480 if (!audio_streams_cached_data_.HasKey(cache_key)) {
481 audio_streams_cached_data_.Set(cache_key, value->DeepCopy());
[email protected]69946cf2013-11-27 00:11:42482 return;
483 }
484
485 base::DictionaryValue* existing_dict = NULL;
mcasasfcb5c7de2014-10-11 20:22:17486 CHECK(audio_streams_cached_data_.GetDictionary(cache_key, &existing_dict));
[email protected]69946cf2013-11-27 00:11:42487 existing_dict->MergeDictionary(value);
488}
489
mcasasfcb5c7de2014-10-11 20:22:17490void MediaInternals::SendUpdateAndPurgeAudioStreamCache(
[email protected]69946cf2013-11-27 00:11:42491 const std::string& cache_key,
492 const std::string& function,
493 const base::DictionaryValue* value) {
494 SendUpdate(SerializeUpdate(function, value));
495
496 base::AutoLock auto_lock(lock_);
497 scoped_ptr<base::Value> out_value;
mcasasfcb5c7de2014-10-11 20:22:17498 CHECK(audio_streams_cached_data_.Remove(cache_key, &out_value));
[email protected]69946cf2013-11-27 00:11:42499}
500
[email protected]11158e2d2013-02-01 02:31:56501} // namespace content